Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Stop Telling Students Recursion is Hard (jinfiesto.posterous.com)
83 points by jinfiesto on April 13, 2011 | hide | past | favorite | 107 comments


When I was writing Ansi Common Lisp I spent a while thinking about why students find recursion a difficult concept. I decided it was because they think of a function as a machine for doing something. E.g. the length function as a machine that finds the lengths of lists. If you think of a function instead as a definition-- e.g. as the definition of length-- then suddenly it's not difficult anymore.

At least, that was the transition I had made myself. My initial difficulty in understanding recursion was probably exacerbated by the fact that the professors themselves (at Cornell in the 1980s) had a mental model of computing that was very close to the machine, and thought the way to understand recursion was to visualize the state of the stack over time as a recursive function computed it result.


Yes, recursion is tied up with baggage about stack allocation. If recursion is approached as inductive reasoning, those objections never occur.

This particularly applies to tail-call optimizations and continuations. If somebody isn't thinking in terms of implementations, continuations are just "you pass in who to return the result to, as an argument." e.g. If it succeeds, tell this function the result, otherwise tell this function the error.

Yet another case of making concepts hard by knowing too much.


Why that is the best simple explanation of continuations I have come across yet. Totally right, it's like thinking about how you are able to walk, if you do it trips you up. Dont think about the details just the concepts.


It's not something people are used to talking explicitly about; it's so ingrained that stuff returns directly to what called it that it never occurs to them.

Exceptions are probably the only exception, but I'm not aware of any language besides Common Lisp where an error doesn't unwind the stack - "an exception makes it return several levels up, where the last handler was defined" still fits within a fundamentally stack-based mental model.


Actually, that description of exceptions requires a stack-based mental model. With true continuations, you have other possibilities for handling the exception (see for example Haskell).


That's _exactly_ my point. Exceptions (as typically implemented) fit within a stack-based mental model, Common Lisp's resumable exceptions and first-class continuations do not.


It sounds like your professors suffer from the same problem that many math professors do. Namely lecturing to an imagined audience that is conversant with intellectual debates that the actual audience is entirely ignorant of.

In the case of math professors this results in lectures where the professor gives detailed proofs despite knowing that nobody in the class followed it, you can't dare test it, and the whole class wondered what the point was. Yet the proofs are rigorous and address points that it took mathematicians decades to figure out.

In the case of your CS profs, in the 1980s I imagine that they were coming out of an era where many computer languages did not support recursion at all because doing so was too inefficient. And therefore they focused very much on exactly how you could implement them, and what the efficiency was.


Why, though, do they think of a function as a machine for doing something?

Because we tell them so.

Before starting any standard CS curriculum (ignoring, for a moment, self-taught hackers), the only place a student should have heard about "functions" is in math class---where they are definitions. They get to intro Java and we tell them "no, no, that's not a function, this is a function, look, it's got variables and for loops and increments and a bunch of returns all through it, this is the real stuff, remember it!" We un-teach them about recursion, and later, when we try to re-teach it to them, we have to undo all of our previous work.

In the past, there was considerable merit to un-learning the inefficient definition-style functions and learning to think closer to the machine. Today, I think there's a lot more to be gained by sticking with the math-style function-as-a-definition, because we have efficient abstractions to deal with it, but of course, this is one of the friendliest forums for that sort of philosophy.

FWIW, I propose that all of academia make a distinction between pure, recursive, definition-style "functions", and imperative, stateful "methods". Probably won't catch on :-/


Your "methods" have also been called "procedures".


sure. pick one and make people use it


The problem with doing away with the implementation specific details of recursion is that these details are inextricably linked to the correctness of your program in most languages.

The fact is, even with modern optimizing compilers that prove all sorts of correctness theorems about transformations, stack overflows will occur in recursive programs if you aren't constantly aware of their existence. You always have to code recursive programs defensively.

A program coded like "recurse(n-1)" will probably blow its lid if you pass in 1,000,000 as the parameter while "for (i=0; i<N; i++)" will happily chug away a billion times. Or worse, if you call "recurse(n-1)" twice in the same function and then pass in a measly value of 100 as the parameter, you will be waiting for the program to terminate on your deathbed.

Explaining why these differences are important to a beginning student, let alone teaching them how to avoid them on their own, is a task that can only get in the way. Avoiding recursion for a while is a much better way to make first few attempts at independent coding not go up in flames.


I just tried, and ghci (i.e. Haskell) does not blow up when confronted with

    > let f n = if n > 0 then n + f (n - 1) else 0
    > f 1000000


...which is why we should be using lazy languages.


Although lazyness is the usual suspect, it has nothing to do with this.


Robert Harper from CMU, eloquently expresses the same notion: "One of the classic pitfalls of teaching recursion is to try to explain it by referring to some mysterious entity called a “stack” that is absolutely invisible in the program you write. There’s a long song and dance about “frames” and “procedure calls” and “pushing” and “popping” things on this “stack” of which the professor speaks, and no one understands a damn thing. It all sounds very complicated, and impressive in a way, but if there’s one thing people learn, it’s to stay the hell away from it! And in doing so, students are robbed of one of the most powerful and useful programming tools available to them, the key to writing clean, reliable code". more: http://existentialtype.wordpress.com/2011/03/21/the-dog-that...


It's funny. I've JUST finished spending a week or so on recursion with my students. The FIRST thing I did was have the entire class repeat after me: "Recursion isn't hard. It's just different."

And I've been teaching CS at the high-school level for 13(?) years now, and I've never had a student who just couldn't get recursion. On the contrary, most of the them pick it up pretty quickly. There's initial confusion, followed by the flash of insight, and then they're pretty much golden.


As a programmer of more than a decade who fell in love with CS in high school, thank you for teaching the topic to others!


Long shot here, but do you happen to teach at a certain engineering school in Philadelphia?

I think I had a CS prof who introduced recursion with this phrase, and it sounds like the class schedule matches up...


I've found this to be the case too, I think telling students that recursion is difficult artificially limits their ability to comprehend it.


When teaching our students concurrent programming using Erlang we never actually mentioned recursion and state immutability. With some careful hand-holding in the first exercises, most of the students seemed to be able to do fine. During three years of teaching the class, only two students ever asked me about immutability ("why can't I change the value of this variable?"). Recursion went similarly unnoticed, it was just the way one programmed in Erlang. Still, a lot of the code produced in the assignments was not very good, but that is to be expected from the course level and the students.

There are two main reasons why I think that most students picked it up without too big troubles. First is that we are just using these things as part of a language while focusing on other topics (concurrent programing). The other is that Erlang is language with such a different syntax from the normal Java-fare that one naturally expects to not be able to do things in the same way.


That's probably the best approach. If you tell students it's 'not hard', they'll immediately suspect your motives and come to the conclusion it actually is hard. If you tell them it's hard, most will believe you. Probably better just to not qualify it and slip it into the lesson when they're not expecting it.


When learning recursion, there aren't much practice problems people do in imperative languages. Most of the imperative languages don't have tail call optimization and the prevalent culture is to use iteration.

That said, there are some problems for which the iterative solution won't be straight forward viz. quicksort, mergesort, binary tree traversal etc etc. But the issue is when a student not very familiar with recursion stumbles upon these, he has a hard time understanding them.

I learned recursion and manual recursion removal in C. I already understood recursion when I read "The Little Schemer", but it still managed to change my perception. I highly recommend it, regardless of whether you are going to work in Scheme(most likely no) or not.


Same experience here, though I learned recursion in Pascal. Maybe it's just me, it just feels awkward when you do recursion in imperative languages. For the past two years, I've been playing/working with functional languages, firstly Erlang or more recently F#, as well as functional programming in C#, and start to appreciate the power of FP. Recursion is also used more often as it just feels natural there when you do recursion in FP.


/second TLS. Most of the book is on recursion, and uses a Socratic teaching method to both explain and reinforce it brilliantly.


Interestingly enough, in curriculums that start out with functional languages (e.g. Scheme) students have little trouble with recursion... and then consider iterative loops to be "hard".


Wow, that's surprising, I'd love to hear about some (anecdotal) evidence supporting this.


In college they tried to teach us recursion using C and I didn't get it. Later I discovered The Little Schemer and SICP and it just clicked. It's easier to do in Scheme/Lisp, and those books also explain it better. There's one anecdote. :)


I heard Matthias Felleisen saying the same.


Recursion is a natural idea. When humans perform repetitive tasks, we don't assign state variables, and we generally don't keep counters. We just keep doing the same thing over and over until we arrive at some kind of terminating condition.

That's a while loop. To eat a bowl of Cheerios, keep spooning Cheerios into your mouth while there are more Cheerios in the bowl. Telling students that recursion is hard isn't a good idea, but telling them that it's a familiar idea that they already implicitly understand isn't a good idea either. The way people normally conceive of iterative processes does not contain the essential element of recursion: that something is used in its own definition. The recursive definition of eating a bowl of Cheerios is: if there are Cheerios in the bowl, you're done; otherwise, spoon some Cheerios into your mouth and then eat the bowl of Cheerios. Most people encountering recursion in a class have never thought that way before. (Jokes are perhaps the only counterexample, though I can't think of any right now.)

That doesn't mean it's hopeless. As another poster said, recursion isn't difficult. It's a simple idea. It's just different. "Different" has big consequences, though. In interviews, I give candidates a programming problem that is solvable using recursion. Very few people come close to solving it -- mostly I judge people by how they approach the problem, not whether they eventually figure out the solution -- but usually, after figuring out (perhaps with lots of prompting) that the problem can't be solved with nested for loops, they manage to volunteer the word "recursion."

Now here's the kicker: about half the candidates who say they're going to solve the problem "recursively" do not actually attempt a recursive solution, not even an incorrect one. They seem to associate recursion with defining functions, because they begin their "recursive" solution by defining a function. However, they do not define a function that calls itself. They do not define mutually recursive functions, either. These are applicants for a senior technical position. They aren't washouts, they're people who have had years of development experience at some point in their careers.

If half the people who are technically inclined enough to choose a career in development, stick with it for many years, and advance to a senior position have a hard time with recursion.... No, if those people don't even understand the basic idea behind one of the most famous concepts in their field (a concept whose name they can still recall despite never or rarely needing to use it since college) then it is not a way of thinking that people develop outside of math and computer science. It isn't a skill that people naturally apply in other domains but have a hard time applying to programming. It isn't just a problem of making a connection between the word "recursion" and a cognitive skill that everybody has. No, it's an alien way of thinking that only becomes familiar through sufficient practice. Until it becomes a natural way of thinking, it's just a definition that can be forgotten as easily as forgetting the year the Magna Carta was signed.

Iterative solutions do follow familiar thinking patterns. Write a sentence on the chalkboard 100 times. Chop on the tree until the tree falls down. Check every line in your credit card statement. Let's drink every one of these bottles of beer. When students write a program that has a for loop or a while loop, they're expressing something familiar they've done countless times before. They're just doing it in a new and unfamiliar form. Even many of the common constructs in functional programs have been drilled into kids since grade school. Map: examine every child's head for lice. Filter: make a list of everyone who didn't turn in their permission slip. Those ways of thinking are thoroughly ingrained, and students who drop CS 101 and become art history majors won't forget how to think that way just because they don't write programs.

Teaching kids recursion before iterative constructs might be a good idea on balance, but let me play devil's advocate. Teaching iterative constructs first has one big advantage: while the students are grappling with the novelty of describing a process in the form of code, at least they are describing familiar processes they understand. Teaching them recursion first means they will be trying to learn the mechanics of writing a program at the same time they're trying to learn recursion. When they have difficulties, they might not be able to figure out which concept they're getting wrong. Why not teach them to program iterative constructions first, and then, after they have some confidence with the task of writing a complex program in a programming language, let them tackle the challenge of recursion? That way they always have something familiar to lean on while they're struggling.

I sympathize with the basic motivation whenever anyone proposes teaching recursion before iteration, which is to keep the kids dependent on their skill with recursion. As soon as they figure out that for and while loops are sufficient to express all the programming ideas they can come up with, they won't want to learn anything else. If recursion is their only tool for expressing simple programs, they'll keep struggling with it until they learn it. Unfortunately, that's not going to work. Kids have always been able to read ahead in the book, and now the first place they'll look is the internet, where other students will let the cat out of the bag. You're going to have to persuade them that it's worth struggling with until they understand it.


> but telling them that it's a familiar idea that they already implicitly understand isn't a good idea either.

But people do implicitly understand recursion. Your ancestors are your parents and their ancestors. Your descendants are your children and their descendants. Most people understand the previous two sentences.

> As soon as they figure out that for and while loops are sufficient to express all the programming ideas they can come up with

There are plenty of problems for which recursion is the most natural solution. For example, building HTML for nested comments is easy with recursion and damned difficult without.


Another algorithm which is much simpler in its recursive form is a flood-fill - paint the screen area with a given color till you hit a "border".


Any self-recursive algorithm (and possibly mutually-recursive - I haven't thought about it enough) can be expressed with an iterative algorithm if you use a stack-based structure to maintain state.


Yes, that's essentially implied by the Church-Turing thesis. :)


I've been hearing experienced programmers opine for a long time that while is the more natural representation of repetitive tasks. Twelve years ago my oldest was learning to walk upstairs. I watched her say "up", go up one stair, then say "up" again, and go up the next stair. This concrete example of recursively calling the "up" function did more to convince me that recursion is natural than any explanation with abstract words like "keep doing the same thing".

>As soon as they figure out that for and while loops are sufficient to express all the programming ideas they can come up with, they won't want to learn anything else.

This is contrary to my firsthand experience. As a teenager, I did BASIC programming. I had a magazine (printed on paper; I'm dating myself) with a BASIC program listing that generated mazes. I could not wrap my head around what that program was doing. At some point my parents bought me FORTH, which does have recursion. That was sufficiently expressive for me to understand how to generate a maze, and even write the program myself without an example. Yes, it's possible to express any programming idea with loops, but many ideas are much more easily expressed with recursion.


Both iteration and recursion result in repeated operations. Either way you define the operation of going up a flight of stairs, it's going to involve going "up" a step over and over again, so there's no way to know which way your kid was thinking just from the fact that she said "up" over and over again. In the "while" scenario, the higher-level function "up the staircase" is done once and the lower-level function "up the next step" is performed many times to accomplish it. In the recursive scenario, "up the stairs" is defined in terms of "up the stairs." Climbing the last step is "up the stairs" and it is also part of the "up the stairs" operation of climbing the last two steps, which is part of the "up the stairs" operation of climbing the last three steps.

So if your kid was thinking recursively, the last "up" operation would be part of the first "up" operation and also part of every intervening "up" operation. If you asked your kid on the last step of a twelve-step staircase how many "up" she was doing, she presumably wouldn't answer "twelve;" she would answer "one." Would that be because she performed the obvious optimization of a tail-recursive function, or because she wasn't actually thinking recursively?


Thinking recusively doesn't necessitate a mental model of the stack. See pg's comment.


Obviously not -- recursion isn't limited to computing. However, nested meanings is inherent in recursive definitions because recursion means defining something in terms of itself. If you come up with a recursive idea that happens to be amenable to tail-call optimization, then you will be able to forget about the nesting, but that only happens through work or luck. Forgetting about the nesting is an implementation hack. Consider the classic example of the Von Neumann ordinals:

    0 = {}
    n + 1 = n ∪ {n}

    3 = 2 ∪ {2} = 1 ∪ {1} ∪ {1 ∪ {1}} etc.
Or even consider the most natural definition of the factorial function, which is not tail-recursive either:

    f(1) = 1
    f(n+1) = (n+1) * f(n)
As the factorial function illustrates, the most natural recursive definition is often not tail-recursive. If you're trying to turn a recursive function into a form that can be efficiently executed as a computer program, you often have to transform the function into a form that is amenable to tail-call optimization. That's something that only programmers care about, though. Or, perhaps, programmers and babies.

If you believe that people naturally conceive of recursive ideas, then what happens when their ideas aren't easily transformed to a tail-recursive form? Is that why babies cry all the time? Perhaps by the time we are small children we have learned to subconsciously discard all recursive ideas that we can't transform into a tail-recursive form? If there is a part of my brain that naturally generates tail-recursive solutions to problems but won't plague me with frustrating non-tail-recursive solutions, I would love to tap into it.


As pointed out in your parent, that still seems like a loop— wouldn't recursion have been your daughter saying "climb the stairs" before each step?


She was too young for sentences. For her, "up" was not some standard library function she could call from a loop. It was the very procedure she was in the middle of defining.

  (define (up)
     (and (stair-in-front-of-me)
          (walk stair)
          (up)))


That's certainly a way of representing it. But surely it's equivalent in practice (as long as the next step can't be a nested flight of stairs) to the iterative approach:

  while there are stairs in front of me
      climb one stair
I guess it's just not as clear to me as it is to you that the "up" utterance is associated with the "climb the stairs" procedure rather than the "climb one stair" procedure.


I agree that it's equivalent in practice; both programs could compile to the same machine code. Where it's different is the human thinking represented by the two programs.

The word "while" implies thinking about a range of time from beginning to end. In contrast, the non-"while" version lets you stay in the moment. It's a conceptually simpler program, up until the point we use that fancy mathematical r-word to describe it.


The word "while" implies thinking about a range of time from beginning to end. In contrast, the non-"while" version lets you stay in the moment.

I disagree. The word "while" does connote time, but it's just the word chosen by early programmers to describe a process of performing an action immediately depending on current conditions, with no knowledge of history. Recursion amounts to the same thing -- after you make the mental leap of a tail-recursive optimization. When you climb the next step, you're climbing it because it's part of the definition of climbing the rest of the steps, which was part of the definition of climbing the rest of the steps when you were on the previous step. As I pointed out in another comment, when you're climbing the last step of a twelve-step staircase, you're actually performing twelve "climb up the steps" operations that are nested like Russian dolls. Thinking recursively is only simple when you learn to mentally simplify all of that, basically to think in terms of the "same machine code" you mention in your comment. I don't think we arrive at that "machine code" starting from a recursive definition of climbing the stairs. It seems much more likely that we figure out the implementation (the while loop) long before we figure out (if we ever do) that we can derive that implementation from a recursive definition of the task.


They are only nested given the common assumption (among programmers, at least) that recursion a stack-like model. From the young child's perspective this is probably tail-recursive operation:

  def up():
     if not at top stair:
         up()
Think of recursion in terms of induction. No nested state required.


You're still talking about state, yes? In either case you see a flight of stairs, you start executing "climb the stairs" and that continues until you are no longer climbing stairs.

It seems to me that "climb one stair as long as there are stairs" is a conceptually simpler definition than "climb one stair and then climb the stairs if there are still stairs."

I think my point still is I don't see how you're differentiating between these procedures in your daughter's mind— why you're assuming that "up" means "I need to climb all of these stairs" rather than "I need to climb this one stair in front of me."

(If indeed it makes sense to model human behavior either way at all; it probably makes the most sense to think of it more as an event loop...)


Recursion is a natural idea. When humans perform repetitive tasks, we don't assign state variables, and we generally don't keep counters. We just keep doing the same thing over and over until we arrive at some kind of terminating condition.

That's a while loop.

Or tail-recursion. I think a more natural example of recursion would be solving a maze. To solve the maze we pick a path we think likely to lead to the exit, if we reach a dead end we back track and try another path.


When I have to eat a whole bowl of Cheerios, I'm usually overwhelmed by its size. So I try eating just a spoonful. Lo' and behold, there are less Cheerios now, but still too many to eat them all at once. So I try just another spoonful...


On the one hand, I'm in total agreement that we shouldn't tell students recursion is hard. It's a very natural analog to proof by induction, after all. It's an important basic component of programming.

On the other hand, "IMHO, recursion is much more natural than iteration and ought to be taught first" is just nuts. I mean, look at the first definition of his example function at http://en.wikipedia.org/wiki/Exponentiation -- it's iteration, pure and simple. And in most non-functional languages, it's trivial to implement that way, and will probably be more efficient than a recursive solution.


I don't think the example I've given is iterative. It generates a recursive process.

You're right on a different point though, this example is much more efficiently implemented iteratively. The point was to illustrate that writing recursive functions is not difficult.

I guess we don't see eye to eye on recursion being taught first. :p In my opinion, recursions analog to "the real world" is much more intuitively obvious than iteration.


Actually, I think your example generates an iterative process, not a recursive process. In other words, according to what I read in SICP recently (working through it the first time), I think your example is a recursive function that generates an iterative process.

It's an iterative process because incoming function arguments capture a snapshot of its state at any point, and therefore you don't need state that exists deeper on the call stack. The upshot is that iterative processes are more efficient in terms of how much stack space they use.

Not trying to nitpick -- I enjoyed your post and it is still relevant. Also, I could be wrong, so someone please correct me if so.


I think parent was referring to the definition on the Wikipedia page:

"When n is a positive integer, exponentiation corresponds to repeated multiplication."

And then an equation that suggests an iterative approach.


Exactly. Good catch.


int pow(int b, int e) { return b * ((e==1)?1:pow(b,e-1)); }


Yes, the author defines exponentiation in terms of recursion. My point is that is not the natural definition of exponentiation, and my evidence is the Wikipedia page, which (as of the time of my original posting of the link, anyway), gives an iterative definition of exponentiation first -- the exact same one I learned in grade school -- and then several pages later mentions a recursive definition as a one-sentence aside.


I'm having problems finding the examples you mention in the wikipedia article. That notwithstanding, I disagree that either method is "natural." Iteration is just what they taught you as a child. If we taught recursion instead (specifically for exponents), then that would be the "natural" method.

The point with my C function is that it's certainly simple to do in a non-functional language.


I don't think that teachers are telling students recursion is hard. I think that through observations, good teachers who have taught for many years have seen that many, many students will just never get recursion. And that's the basis of teachers saying that recursion is hard.


The idea behind recursion is I have X, if I know Y then this would be easy. But, picking the correct Y and finding a simple path to get Y is hard for many people.

Consider something as simple as factorials. 1! = 1 and N! = N * N-1! but try to code it as: N! = N! / 1! * 1 = N! / 2! * 2 = N! / 3! * 6. Then with a lot of time and effort they get something that works, but it's not intuitive.


Perhaps the other problem is that it's not obvious to students what the point of doing things recursively is. OK, it can shave a few characters off your factorization code. Or your Fibonacci code. That's neat, but why get excited?


I usually show them a recursive Towers of Hanoi solver. It's alarmingly simple (fewer than 10 LOC) and most of them can't even imagine how to write it iteratively.

I tell them that recursive code is rarely better than iterative code, but for certain types of problems, it's SO much easier to come up with a recursive solution that it's worth knowing how to do.


I am not sure if I understand recursion or not. My definition of recursion is a function that calls itself repeatedly until an exit condition occurs (the first chapters of little schemer define this as an empty list). If the exit condition never occurs, and the computer lacks infinite computing power, you will probably get a stack overflow. As far as I can tell, recursion and a for-loop accomplish the same thing.

Am I missing anything?

EDIT: I wrote this before reading the article


You aren't missing anything, but recursion can get more complicated. In the case of towers of Hanoi, or recursion on trees, the iterative version using a for-loop becomes substantially more complicated than the recursive version.

Also, in Scheme and other functional languages, function calls in the tail position are compiled or otherwise treated as jumps. So in Scheme this will actually produce an infinite loop with no stack overflow:

  (define (loop)
    (loop))
  (loop)


Thanks for your answer.

>Also, in Scheme and other functional languages, function calls in the tail position are compiled or otherwise treated as jumps.

So that is why people make such a big deal about tail-call optimization? Because it prevents stack overflow?


Yes.


I think you're forgetting that the function can have more than one recursive call site within its body. A for-loop is identical to a recursive function containing only one call site within its body.

By contrast, in order to calculate the number of nodes in a binary tree, you would do

    (nodes (tree)
        (if (tree? tree)
            (1+ (nodes (left-node tree))
                (nodes (right-node tree)))
             0))
which is not like a simple for loop at all.


I tell them that recursive code is rarely better than iterative code

As a general statement, this is wrong. Recursive code is almost always better than imperative code in languages designed to encourage recursion -- I'm thinking of functional languages here, of course. Please qualify your statements to your students, lest they get the idea that it is a problem with recursion as a principle rather than a problem with the language they're working in.


Solving a maze is a classic. The recursive solution is the obvious/intuitive one.


Again, mazes and Towers of Hanoi are neat, but still not exactly what you'd call problems with real-world applicability. There must be some out there -- some types of parsers, perhaps? Are there any matrix operations which are best done recursively?

I just think that recursion falls into the category of things that professors think are neat rather than the category of things that are useful to students. Some students will love that kind of thing, others will wonder what the hell the point is.


Any "divide and conquer" algorithm is likely to be recursive. Thus, for instance, the FFT, the Karatsuba multiplication algorithm for large integers, Strassen's matrix multiplication algorithm for matrices and the like are all recursive.

Moving on, dynamic programming is a very important technique. About half the time it is easier for me to figure out a dp solution by writing a recursive solution with memoization than it is to build the solution bottom up.

The single most studied problem in computer science is sorting, for the simple reason that a surprising fraction of computing time spent is spent doing sort operations. Virtually every efficient sort algorithm uses recursion somewhere.

The fact that a lot of students won't go on to use recursion doesn't mean that it isn't a very useful technique.


You can talk about "divide and conquer" but the most common problem where recursion really helps is looking at tree data structures. As then to consider looking for the total of some value stored in an XML tree.

In an iterative language it's a for loop and then calling the function on each of the children. In a Lisp it's calling it's self on the child and next node.

PS: Showing someone they can transform a recursion function into a while loop if they keep track of their own stack seems to help. Yea, you can write it that way, but recursion really is simpler. (This also helps explain what a stack really is.)


Yes, recursion is a natural way to walk through a recursive data structure, like a tree. However if you only know how to reach for recursion, you're completely hosed the second you need a breadth-first search instead. (I've watched candidates completely collapse when faced with that problem.)


A breadth-first search can be easily handled with recursion (in fact, I'd argue the specification is almost as simple):

  def bfs(search_node, nodes_to_visit):
      node = nodes_to_visit.pop()
      if node == search_node:
          return node
      else:
          nodes_to_visit.extend(node.neighbors)
          return bfs(search_node, nodes_to_visit)
 
  bfs(search_node, [root_node])


If it is so easy, why does your implementation crash if the element is not in the tree? :-P

After you fix that minor bug, you'll find that your code crashes for trees with over 1000 nodes. That is also easy to fix. But in any language without tail recursion, your implementation hits the stack hard. Which in many multi-threaded environments may not be not a wise thing to do.

All that said, I submit that you easily think of that version because you're familiar with how the recursive solution manages the stack. If you're familiar with that, then switching from dfs to bfs is just a question of replacing a stack with a queue. Compare:

  def dfs(search_node, root_node):
      nodes_to_visit = [root_node]
      while 0 < len(nodes_to_visit):
          node = nodes_to_visit.pop()
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
      return None
  
  def bfs(search_node, root_node):
      nodes_to_visit = [root_node]
      while 0 < len(nodes_to_visit):
          node = nodes_to_visit.pop(0)
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
      return None
Now contrast the two obvious recursive variations.

  def dfs_recursive(search_node, root_node):
      if search_node == root_node:
          return root_node
      for child_node in root_node.children:
          answer = dfs_recursive(search_node, child_node)
          if answer is not None:
              return answer
      return None

  def bfs_recursive(search_node, root_node):
      nodes_to_visit = []
      def _recurse():
          if 0 == len(nodes_to_visit):
              return None
          node = nodes_to_visit.pop()
          if node == search_node:
              return node
          else:
              nodes_to_visit.extend(node.children)
              return _recurse()
      
      return _recurse()


Well, I agree with you there. Any language that can't handle proper tail recursion makes it really difficult to safely implement a recursive solution. But I often find it easier to solve a problem with a recursive solution and perform an almost mechanical conversion into the corresponding iterative solution. (also I'm embarrassed that I forgot the edge case where the node is not in the tree)


We all make silly errors. Your bfs was a complicated dfs, and neither of us noticed. (In fact I copied your code and edited it, and had dfs and bfs reversed.)

Luckily I was able to edit my node to hide my variation of the error.


Yes, exactly, recursive descent parsers are what they sound like. I'm not very up on other types of parsers, but this is the kind I learned about, and I would say they are very intuitive.

http://en.wikipedia.org/wiki/Recursive_descent_parser


Graph problems would be hard without recursion I think. I've been working on some problems that are naturally expressed as graphs, and recursion is the natural way to do some things; it's be quite hard to do some things I did iteratively, now that I think about it (that is, without 'emulating' recursion with an 'explicit', manually-maintained stack).


Yes. Though breadth-first approaches look much more imperative than depth-first traversals. That's because basically you exchange the stack for a queue. (Those algorithms can still look nice in, say, Haskell.)


If you're writing a 3d engine, you may encounter many tree-like structures and recursive operations on them: BSPs, quadtrees, octrees, triangle trees for variable LOD terrain...


One of the projects in my CS program using recursion was a 6-degrees of Kevin Bacon game. It was neat and real-world practical IMO.


I'd have said quicksort is an example of an algorithm where the easiest way to think of is recursively.


And while you're at it, quickselect, too.


This is the reason why I'm never get excited about recursion. I hardly ever find a real world application for it aside from a tree transversal.

Most examples are toys (fib, towers of hanoi, maze solving) or problems that are already solved, sorting, searching, etc.


Try writing a program that does complex things with trees (directory structures, for example) without using recursion.


Probably the best HN comment on recursion: http://news.ycombinator.com/item?id=2440869



It might be easier to teach them about functions that can call themselves first; and then explain the concept of recursion in the context of that (or just that it is called 'recursion').

A recursive function call is conceptually no different than a regular function call.


There's more to recursion than functions. Data types (like linked lists) can also be recursive.


A lot of the disagreement here comes from what people consider recursion. As in all things, the basic idea behind recursion is quite simple to understand. It's just a function calling itself.

There are, however, some non-trivial uses of recursion such as quicksort and analyzing its runtime.

Thus, to say simple recursion isn't hard is true. But I'm sure that we can all find a recursive problem that we wouldn't consider easy.


Agreed - the problem is not recursion, but that it becomes useful for actual complex problems.

I understand recursion pretty well at a conceptual level (my background is math, mostly self-taught for CS), but I have a hard time using it for programming. Sure, fibonacci or quicksort are simple, but tree recursion or application to string processing (e.g. for edit distance compuation) is quite harder.


I am the only one who gets upset when fibonacci sequence calculation is used as an example of recursion? Are there other possible examples which are simpler than quicksort, but actually make sense if done with recursion instead of simple loop?


No, I think it is a terrible example at so many levels it is not even funny. The only interesting thing about that example is how almost any other solution is much better.

I would consider quicksort to be a quite simple example if you use a high level language. But search in binary search tree and similar tree/graph traversal are maybe better examples, in the sense that most people would find recursion to be more natural than any iterative solution.


> As in all things, the basic idea behind recursion is quite simple to understand. It's just a function calling itself.

That's not the whole story. There's more to recursion than that. You can have mutual recursion, and recursion in things other than functions: e.g. data structures, or the structure of the Mandelbrot set or that of the fern-like fractals.


Actually, this is not correct. While recursion is typically implemented in the form of a function which calls itself, that is not exactly what recursion means. In fact, "recursion" derives from "recur", which simply means to repeat, or re-occur, which, when you look at it, is exactly what a "recursive" function is designed to do (without all that messy for/next, do/while jazz). Calling itself is just a particularly clever, interesting, and elegant way to do that.

To really illustrate the point of recursion, consider showing your students one of the simplest recursive algorithms out there. You can find it on the back of many shampoo bottles:

  * Lather.
  * Rinse.
  * Repeat.


Sound like your definition of recursion fits any loop. I doubt that's what recursion means in CS.


That would be because recursion and loops are isomorphic. Anything you can do with recursion can be done in a loop and vice versa. It's just that one or the other may be clearer or easier depending on what you're doing.


This is why most looping structurues can be replaced with recursion. It's also why you hear of "unrolling" tail-recursive calls into more traditional looping structures. They're equivalent.


LOGO, a programming language for teaching computing to young children, uses recursion as its fundamental control structure. My kids studied it successfully in a Philadelphia public school on C64s in the 1980s.


In my first year programming course, our prof spent 10-15 minutes at most talking about recursion. I was already familiar with it, but my classmates (mostly non-CS majors) had no problems understanding the concept. Since that experience I'm always slightly amused when I hear people saying that teaching basics of recursion is hard.

Perhaps it isn't taught properly? When you're just starting out with it, there really isn't much to teach though.


You say they "understood" the concept. Was this understanding tested in some way?


Am I along in never being "taught" recursion? I mean, throughout my curriculum, I've used recursion many times and dealt with everything from fibonacci to quicksort, but never once have I sat through even part of a lecture on "Recursion". Among my peers, I don't think anyone really has an issue with recursion or how it works. Harder to debug possibly, but everyone "gets" it, I feel.


Shouldn't it be easier to debug, because you have stack traces?


Recursion is harder in some languages than others (e.g. python--a commonly recommended beginner language--where recursion is deliberately broken)


I always found talking about stories about stories about stories a very easy way to get it across. Humans intrinsically get stories.


Interesting article on recursion... http://tenaciousc.com/?p=1000


Getting tail recursion right IS hard.


I think the main fault of recursion is that students end up with "stack overflow". The JVM and C++ also don't have tail recursion. Performance is really the biggest perception difficulty. Would also help if students learned about inductive proofs in mathematics earlier on.


Telling them that it is cost intensive would be better!


That depends on how you write your recursion, what your compiler does with it and what you use as cost function (programmer productivity?). That's too many 'depends' to tell students about, I think.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: