A Fibonacci sequence is defined as follow: $F(n) = F(n-1) + F(n-2)$ $F_1 = 1$ , $F_2 = 1$
This first solution solves the Fibonacci sequence using a for loop. If we count only the arithmetic operations, the solution has a complexity in $o(n)$ ($n-3$ additions in the for loop).
While algorithmic complexity is one criterion to evaluate the efficiency of an algorithm, a second criterion is the space complexity, i.e. the amount of memory used by the program. In the previous solution, we stored the $n$ elements of the fibonacci serie in an array, even though technically we would only need to store the rolling last two values. The version below adapts the previous solution by storing only the last two values to decrease the space complexity of the algorithm.
The version below is a naive recursive solution, if you run it you will see that it takes a lot of time to solve a Fibonacci sequence, even when $n$ is $< 50$.
The reason it takes time is that we compute the same solutions multiple times. As an example, the image below represents the call stack when $n = 5$. We can see that for $n = 1, 2$ and $3$, our naive recursive solution computes the value multiple times.
Finally, below we propose another recursive solution that exploits tail recursivity. A tail recursion is a recursive function in which no computation is done after the return of recursive call. Many compilers optimize to change a recursive call to a tail recursive or an iterative call.
The image below presents the call stack for $n = 5$.
As we can see, the solution is more efficient since we don’t compute the same Fibonacci sequences multiple times as it was the case with the previous solution.