
Understanding Recursion as an Absolute Beginner - dev_prashaant
https://www.bigomega.dev/recursion
======
RcouF1uZ4gsC
>Most of the people find recursion difficult but it's not.

Recursion is really hard.

According to

[https://phys.org/news/2019-08-recursive-language-modern-
simu...](https://phys.org/news/2019-08-recursive-language-modern-
simultaneously-years.html)

Recursive language is a relatively recent phenomenon and even now is much
harder to acquire than language and grammar. Recursion is probably the biggest
thing that separates our communication from that of other species and gives
such a big advantage.

That being said, if you are reading HN, you have already mastered a recursive
human language. You have the ability to understand recursion. Just be aware
that it is one of the more complicated things your brain does, and be patient
with yourself. Keep working at it, and eventually you will get it. Don't
become discouraged by the process and give up.

~~~
tempguy9999
While recursive human language and recursion in computing terms are related, I
really don't see they gain much from each other.

> Recursive language is a relatively recent phenomenon

Is there any evidence it's more recent than non-recursive language
acquisition, evolutionarily (rather than developmentally ie child to adult)?

For reference for others in this thread, an example of linguistic recursion is
<[https://en.wikipedia.org](https://en.wikipedia.org)
/wiki/This_Is_the_House_That_Jack_Built>

    
    
        This is the horse and the hound and the horn
        That belonged to the farmer sowing his corn
        That kept the rooster that crowed in the morn
        That woke the judge all shaven and shorn
        That married the man all tattered and torn
        That kissed the maiden all forlorn
        That milked the cow with the crumpled horn
        That tossed the dog that worried the cat
        That killed the rat that ate the malt
        That lay in the house that Jack built.

------
tempsy
I like the dark movie theater analogy for beginners e.g. in a dark theater,
how would you know what row you're sitting in? You'd ask the row in front of
you "what row are you in?" and they'd shake their head and ask the row in
front of them and so on until they finally get to the first row, who would
know they're in the first row, and at which point they can answer the second
row's question all the way back to the row that originally asked the question.

------
kazinator
Recursion is absolutely easier to understand, period. A big reason for using
it is that it's easier to convince ourselves that a recursive solution is
correct; it directly gives us the pieces that we need for inductive reasoning.
The degree to which a module of code is hard to understand is directly related
to the effort required to convince ourselves that it does exactly what it is
supposed to, in all cases.

Recursive solutions are hard to come up with for someone whose perspective is
biased due to having a background in writing imperative code with iteration.
They have already forgotten the difficulties they overcame in acquiring their
existing skills.

Someone who learned nothing but recursion (and pure functional programming)
will likely struggle with loops, gotos and assignments.

------
js2

        countDownFrom(--n)
    

There's no reason to reassign the decremented n within the function. Prefer:

    
    
        countDownFrom(n - 1)

~~~
ChristianBundy
Thank you! This immediately stood out to me as a clever hack that makes it
more complicated than it needs to be. I'd have written this function as:

    
    
        function countDownFrom(n) {
          console.log(n)
        
          if (n > 0) {
            countDownFrom(n - 1)
          }
        }
    

This removes both the decrement operator and the return keyword, both of which
distract from the concept being taught.

This would also make it simple to add a `step` argument, which could be
written as:

    
    
        function countDownFrom (n, step) {
          console.log(n)
        
          if (n > 0) {
            countDownFrom(n - step, step)
          }
        }

~~~
kazinator
coutDownfrom(n--) contains a pointless assignment to a variable that has no
use in the function after the logging call. It's not what I'd call clever.

It spoils the teaching value of the solution, because a beginner might be
confused into thinking that the mutation of _n_ is essential, like that there
is only a single _n_ and it is being stepped to bring about the countdown.

The student needs to understand that the countdown occurs even though there
are no assignments to n, nor anything imperative other than the side effect of
the logging call.

Don't write beginner examples which contain red herrings that must be
explained away to some of the beginners.

~~~
Fishkins
countDownfrom(n--) produces infinite recursion, which is arguably why you
could call coutDownfrom(--n) clever (you have to understand the subtleties of
those operators). I agree that "pointless" or "stupid" are better descriptors,
though :)

------
vinceguidry
A better resource is How To Design Programs. A better title for it would be
"How to design recursive algorithms using Scheme".

[https://htdp.org/](https://htdp.org/)

Every now and again, you need recursion. It doesn't come up too often, simple
maps and the occasional reduce handle 95% of tasks. But every once in awhile
simple iteration isn't good enough, and you'll be glad you'd gone through HtDP
all those years back like I did.

------
polygotdomain
I think recursion is very much about finding the right application to learn
it, rather than trying to shoehorn an example that doesn't wind up needing a
recursive solution.

I wound up needing to learn recursion very early on in my programming journey
because the problem space I was working in at the time (3D computer graphics)
needed it. I found it relatively easy to reason about recursion because the
solutions became simpler, more efficient, and more effective when they were
solved recursively. Finding those problems that clearly need recursion, are
simple enough to grasp, and make good sense are hard, not recursion itself.

The variety of contexts that beginners have and the gravitation of computer
science to tree traversals as a standard example (or a contrived one like
this) is why I think so many get tripped up on recursion when it's introduced.

------
vchak1
"To understand recursion, you must first understand recursion", said Benoit B.
Mandelbrot. When asked about his middle initial he said: it stands for "Benoit
B. Mandelbrot".

------
diminoten
How "absolute beginner" are we talking here? At what point have we distilled a
concept to its bare minimum and failure to understand simply requires the
person to spend more time with the concept?

If recursion is difficult to grasp, I'm afraid it's not going to get any
easier from there.

~~~
DonaldPShimoda
> If recursion is difficult to grasp, I'm afraid it's not going to get any
> easier from there.

I strongly disagree. Many people have never had to reason about recursion so
explicitly before going into CS. Just because it's difficult to understand
explicitly at first doesn't mean they won't get better.

If they _never_ understand recursion, then that's a problem, but I think that
probably says more about the teacher than the student.

~~~
diminoten
What do you strongly disagree with, exactly?

------
rvn1045
It’s way easier as a beginner to understand recursion in lisp imo. You can
just substitute in all the calls and see the whole structure of the recursive
function.

------
cr0sh
I'm not sure when I learned recursion, so I don't know if I can say whether or
not I had any trouble with understanding it.

I do know that once you understand recursion, knowing how to properly change a
routine to a stack or other non-recursive routine (generally because you keep
blowing the return call stack because the nesting level gets too deep) can be
tortuous at times depending on what the routine is doing.

So perhaps that might be an insight to how people facing recursion as a
beginner might feel...?

------
lhnz
When I was a beginner the two problems I had with recursion were:

1\. Not handling the base case and accidentally causing an infinite loop.

2\. Never reaching the base case so ending up in an infiniite loop.

3\. Creating too much incorrect state, and not having a sane way of debugging
or visualising this due to the speed and quantity in which it is created.

Nowadays, I carefully focus on the first two problems, before moving on to
working out a way of visualising the next N function calls, etc.

~~~
DonaldPShimoda
The way I explain implementing recursion is this:

1\. Write down what your function does in a comment. (e.g., "sum all the
elements of a binary tree"). 2\. Write the function signature. 3\. Write the
base case. (This is usually straightforward and most people don't seem to have
an issue with it when reminded.) 4\. Stop thinking. 5\. __Assume your function
already works __and write the recursive case. 6\. Profit!

I find where most people get confused over recursion is in trying to reason
about the recursive case. They think "Well if I start at the root of the tree,
then the next thing I need is to get the children... and then... with the
first child... uh..."

They get lost in the recursion. So I tell them: _assume_ your function already
works. Trust that you will write it correctly eventually. What does your
function do? How can you use that?

If you assume you already have a function that sums binary trees, and if
you're trying to sum the children of a branch node, well then use that
function that you already have to get the sum of each of those children trees,
and add up the results!

I always recommend to never try to visualize N levels of recursion, unless
you're working through an example to actually test your implementation.
Worrying about the deeper recursion is how people get lost and confused. Keep
it simple: just look at the one layer and assume the other layers will do
their job correctly.

------
arc33
I think the simplest way to understand recursion is as a for loop where you
use the stack as the counter.

Recursion is basically implementing the operations of a repetitive loop but
the loop controls are not explicit like in a for loop, instead you use the
stack as the counter of the loop.

Once you think of recursion as just another way to do for loops, it
immediately is demystified.

~~~
DonaldPShimoda
> Once you think of recursion as just another way to do for loops, it
> immediately is demystified.

I actually think this is not a good way to understand recursion. It's like
saying "once you think of the lambda calculus as another way to build a Turing
machine, it immediately is demystified." Yes, it's true that they're equally
powerful, but their differences are what make one or the other more suitable
to certain circumstances.

I think with recursion the best way to show this distinction is to look at
navigation over a recursive structure, like a binary tree. You could implement
any navigation over a binary tree with some nice loops, but they don't really
_mean_ anything in relation to the program: they're just a procedure to get
the job done.

On the other hand, since binary trees are recursively defined (each node is
either a leaf containing data or a branch containing two binary trees), using
structural recursion to navigate the tree is significantly more
straightforward than any looping construct. This is made super clear in a
language like Haskell:

    
    
        data BTree
          = Leaf Int
          | Branch Int BTree BTree
        
        sumTree :: BTree -> Int
        sumTree (Leaf v) = v
        sumTree (Branch v l r) = v + (sumTree l) + (sumTree r)
    

But in, say, Python:

    
    
        @dataclass
        class BTree: pass
        
        @dataclass
        class Leaf(BTree):
          v: int
        
        @dataclass
        class Branch(BTree):
          v: int
          l: BTree
          r: BTree
        
        def sum_tree(t: BTree) -> int:
          stack = [t]
          total = 0
          while stack:
            curr = stack.pop()
            if isinstance(curr, Leaf):
              total += curr.v
            elif isinstance(curr, Branch):
              total += curr.v
              stack.append(curr.l)
              stack.append(curr.r)
            else:
              raise RuntimeError("Invalid BTree subclass.)
          return total
    

The Python solution isn't obviously correct. You'll have to read through that
and make sure I implemented my traversal correctly — assuming you know what an
iterative traversal looks like and you can recognize it without reading the
code more fully first.

In contrast, the Haskell solution is clearly correct at a glance, I think.
Ignoring the difference in length of code, the Haskell is clearly recursing
over the structure of the tree. This is, in my opinion, significantly easier
to reason about than any amount of looping.

I guess the caveat to all this is: recursion is well-suited for certain kinds
of problems (such as navigating a recursive datatype), and loops are well-
suited for different kinds of problems. But saying "they're equivalent so
think of all recursion as loops" is missing the forest for the trees, I think.

------
VladimirGolovin
To anyone trying to understand recursion, I advice to learn basic Haskell.
This is the language where recursion actually "clicked" for me.

All you need is very basic Haskell. You don't need functors, applicatives or
monads. You only need simple functions, pattern matching, and maybe some
parametric polymorphism to simplify the examples and make them more general:

    
    
      fliplist :: [a] -> [a]
      fliplist [] = []
      fliplist (x:xs) = fliplist xs ++ [x]
    
      l = [1,2,3,4,5,6,7]
      f = fliplist l
    
      main = print f

------
alejohausner
The problem with recursion (and with the counting example in the post) is that
a student will ask "why can't I do this with a loop?" It's better to use a
problem where recursion _MUST_ be used, such as a binary tree:

The size (# of nodes) of a binary tree is:

\- 0, if the tree is empty, or

\- size of left subtree + size of right subtree + 1

If you draw a tree, any student will agree that the recursive method makes
sense. Indeed, they would be hard-pressed to come up with solution that uses a
loop (unless they know about stacks, OK OK).

~~~
wittenator
Just a nitpick, loop languages and recursive languages have the same
expressive power. Therefore you can write a loop program for any recursive
one. So there is no "must".

~~~
alejohausner
Fair enough. I should have said "is most appropriate" rather than "must be
used".

------
saagarjha
> To do that we can simply return without returning anything i.e. null.

I'm no JavaScript expert, but a function that returns nothing results in
undefined, no?

~~~
gridlockd
Indeed, null!==undefined and if you want to return null you must _return
null_.

However, null==undefined so you might not even notice the difference.

------
freetonik
I had good results with my students with the illustrated video explanation I
made [1]. It thinks of running functions as boxes that produce output, and of
function definitions as box blueprints.

[1]
[https://www.youtube.com/watch?v=vLhHyGTkjCs](https://www.youtube.com/watch?v=vLhHyGTkjCs)

------
pretendscholar
Is there a good example of a time where recursion would be much more efficient
than iteration?

~~~
rileytg
not necessarily efficient but here’s my latest use case:

scheduling a health check job recursively. if the health check fails, it
schedules itself to run again in X seconds with N-1 runs until giving up and
marking the service as down.

iteration here simply wouldn’t work

~~~
bananasbandanas
Why not?

Just schedule a single health check with a loop that tries to health-check N
times in a loop, with a delay in between. If the check succeeds at any point,
return 'healthy' early.

Or schedule N health checks at different times, if any check succeeds cancel
all others, etc. etc.

------
chiefalchemist
I like recursion; about the same as I like regex. In theory, it feels simple,
and the power relative to that is amazing. In practice, it can be aggravating,
but once solve it feels so damn good :)

------
fortran77
Relevant:
[https://news.ycombinator.com/item?id=20888923](https://news.ycombinator.com/item?id=20888923)

