Recursion is similar to a loop: a procedure is run over and over again until it reaches a stopping point. Recursive methods must call themselves to be considered recursive. We can use recursion anywhere we use a loop, and vice versa. Sometimes, it’s a lot easier to solve a problem using recursion, as opposed to a loop, and sometimes it’s easier to use a loop. It takes time and practice to figure out when to choose one over the other.
def talk_to_myself(n)
talk_to_myself(n)
end
In the above code, the method talk_to_myself
is recursive because it calls itself. It, however, has a gigantic problem: there is no stopping point!
The base case (or cases) tells the recursive method when to stop running. It is often an if statement, though it doesn’t have to be (it depends on the method and what it needs to do).
def talk_to_myself(n)
return if n <= 0.5
talk_to_myself(n / 2)
end
Notice how the base case comes before the recursive call to the method. If the base case came after, it would be unreachable and we’d have the same exact problem as before: there would be no stopping point and we’d hit a stack overflow.
When we run a while loop where the terminating condition is never reached, we get an infinite loop. A stack overflow is similar. However, code that would eventually terminate can also cause a stack overflow if it adds too many frames to the stack. A frame is like a snapshot of all of the variables and other necessary information required to finish running the process. The stack is a data structure that stores frames. Frames are removed from the stack in last-in-first-out (LIFO) order, similar to how we eat a stack of pancakes (the last pancake is put on the stack last, and we eat that one first).
With recursive methods, the last recursive call will complete its execution first. Once that completes, the second to last recursive call will complete, and so on until only the first call to the method remains. Let’s go back to our talk_to_myself
method and illustrate each frame:
def talk_to_myself(n)
return if n <= 0.5
talk_to_myself(n / 2)
end
- Initial Call (execution incomplete, paused on recursive call):
talk_to_myself(4)
- Recursive Call 1 (execution incomplete, paused on recursive call):
talk_to_myself(2)
- Recursive Call 2 (execution incomplete, paused on recursive call):
talk_to_myself(1)
- Recursive Call 3 (execution incomplete, paused on recursive call):
talk_to_myself(0.5)
- => Base case is hit because n <= 0.5, no more recursion!
- Recursive Call 3 completes
- Recursive Call 2 completes
- Recursive Call 1 completes
- Initial Call completes
You can walk through and visualize this process here. The frames and their data are visualized on the right side of the screen and the arrows on the left inside the IDE show you which line is being executed. Notice that the arrow pauses on the recursive call if the base case is not hit. When a recursive call finally completes execution and returns up the stack, the previous call will then continue to run from that line onward (the line where the recursion was triggered).
Let’s go back to our code example and modify it to return the string ‘done’:
def talk_to_myself(n)
return 'done' if n <= 0.5
# this is where our method pauses
# it's also where our return values return to
talk_to_myself(n / 2)
end
That one small change will cause the method to return the string 'done' from every recursive call and the initial call. But how?
Let's illustrate how using stack frames again:
- Initial Call (execution incomplete, paused on recursive call):
talk_to_myself(1)
# pauses on the line of the recursive call
- Recursive Call 1 (execution incomplete, paused on recursive call):
talk_to_myself(0.5)
# pauses on the line of the recursive call
- => Base case is hit because
n <= 0.5
, no more recursion! 'done' is returned to the previous frame - Recursive Call 1 receives 'done', and then returns 'done' up the stack
- Initial Call receives 'done', and then returns 'done'
But what if we added a line of code after the recursive call? What would happen then?
def talk_to_myself(n)
return 'done' if n <= 0.5
# this is where our method pauses
# it's also where our return values return to
talk_to_myself(n / 2)
'The sheep goes baaaaaahhhh'
end
- Initial Call (execution incomplete, paused on recursive call):
talk_to_myself(1)
# pauses on the line of the recursive call
- Recursive Call 1 (execution incomplete, paused on recursive call):
talk_to_myself(0.5)
# pauses on the line of the recursive call
- => Base case is hit because n <= 0.5, no more recursion! 'done' is returned to the previous frame
- Recursive Call 1 receives 'done' and returns 'The sheep goes baaaaaahhhh' up the stack
- Initial Call receives 'The sheep goes baaaaaahhhh', and then returns 'The sheep goes baaaaaahhhh'
You can see a visualization of the code here.
Don't worry if this hasn't all sunk in yet. We'll get you started slowly. If you find yourself having trouble with recursion, ask yourself these questions:
- What is/are the base case/s? Many people add those first.
- If you're getting a stack overflow: Why isn't my base case being triggered?
- What should the recursive call return? And how should I use that value?
- Remember the return value goes up the stack to the line where the recursive call was made.
- What should the method return once it has completed execution?
You can also try drawing out the frames to trace what's happening or use this tool. Start small when mapping out what's happening, e.g. in the code examples above we used the values 2 or 4, but never 20!