-1

I've console.log every line, saw many youtube videos about, but can't wrap my head around it. I need a step by step of what's going on.

To give an example, I understand that return head after the if-statement never gets called, but it should considering that every time that newHead gets declared it calls head.next as the new head in reverseList(head), which should lead to head.next === null.

Below is my code:

let headList = {
  value: 1,
  next: {
    value:2,
    next: {
      value:3,
      next: {
        value:4,
        next: {
          value:5,
          next: null
        }
      }
    }
  }
}

function reverseList(head) {
    if(head == null || head.next == null) {
        return head
    }

    newHead = reverseList(head.next);

    head.next.next = head;
    
    head.next = null;
    return newHead;

};

reverseList(headList)
M-Chen-3
  • 2,036
  • 5
  • 13
  • 34
learn123456
  • 177
  • 1
  • 2
  • 9
  • 1
    the if is to check the actual head, you will never get to that part after the recursive calls kick in. Set breakpoints and go through the code. – EugenSunic Dec 20 '20 at 22:25

1 Answers1

1

return head after the if statement actually does get executed.

In your function body, we almost immediately call it back recursively, so it is like climbing ladder, executing first part of function :

    if(head == null || head.next == null) {
        return head
    }

    newHead = reverseList(head.next);```

For each of our heads:

Head 1 ↩
  Head 2 ↩
    Head 3 ↩
      Head 4 ↩

Now we are at Head 4 and again recursively call function with argument { value: 5, next: null }, but this is the last time we are performing recursion, because we reach function's base case - function argument satisfies if statement, and it returns to Head 4 immediately.

Now we will climb back down this call stack and execute second part of the function for each head on the way down.

 // newHead = reverseList(head.next); <-- Resuming from here on the way back

    head.next.next = head;
    
    head.next = null;
    return newHead;

FREEZE THE TIME now, while we are at Head 4, preparing to climb down the call stack!

Since we passed head.next as the argument to the last recursive function call and got it back unchanged, head.next and newHead is pointing at exactly the same object.

And remember, we are in Head 4 now, so head.next.next = head is the same as newHead.next = head. This means Head 4 comes after Head 5 now! Function returns { value: 5, next: { value: 4, next: null }}.

Let's continue execution and now we are in Head 3.

The reason why we need to write head.next.next instead of newHead.next is because on the way down the call stack we need to attach Head 3 object to Head 4.next property and not newHead.next (since newHead.next already points to Head 4).

head.next.next is like saying 'i want to be in front of the head which was in front of me when we started executing function.

Since Head 3.next references Head 4, head.next.next will put Head 3 in Head 4.next property and that is all we need.

So on the way down Head 4 becomes Head 5.next, Head 3 becomes Head 4.next etc.

Recursive functions can be hard to grasp intuitively so i recommend starting from easier ones, for example here: Recursion in JavaScript Explained Using a freeCodeCamp Challenge.

  • Thanks for the detailed explanation! But there's one part I dont get. When `head` gets returned for the first time, it returns the current head `{ value: 5, next: null }` which assigns to `newHead`. I get that the last value passed by `newHead` was ` {value: 4, next: { value: 5, next: null }}`, but the last `return head` should make it value: 4 – learn123456 Dec 22 '20 at 09:05
  • 1
    The argument passed to the `reverseList` function for the last time is `{ value: 5, next: null }`. Otherwise it would not satisfy `if` condition and function would recurse further. Imagine we are at the moment in time when `reverseList` gets called for the last time, which is `Head 4` in my illustration. Our `head` is `{value: 4, next: { value: 5, next: null }}`. So we call `reverseList` for the last time with `head.next` as an argument, which makes it `{ value: 5, next: null }`. – Simas Butavičius Dec 22 '20 at 10:46
  • Exactly, the last time `newHead = reverseList(head.next)` gets called, `head = {value: 4, next: { value: 5, next: null }} ` , and now the new head in `reverseList` is `{value: 5, next: null} ` , but then the `return head` should assign `{value: 5, next: null } ` to `newHead`, and not only that, but because the last time `reverseList` received ` {value: 5, next: null} ` as `head`, the new `head` for the rest of the code should be ` {value: 5, next: null} `, that's the part I don't get. Thanks for taking the time to answer – learn123456 Dec 22 '20 at 11:03
  • 1
    We change contents of `newHead` on the way back with this line: `head.next.next = head`. Remember that on the way up, `head.next` references "one head higher", for example `Head 3.next` is `Head 4`. It still references the same object on the way back and by modifying `head.next` object we indirectly modify `newHead` object, because `head.next` is inside `newHead` now. If you have difficulty understanding how `head.next.next` is modifying `newHead`, try reading up here: https://javascript.info/object-copy – Simas Butavičius Dec 22 '20 at 11:47