javascriptloopsrecursionobject-reference

How and why parent variable targeting the last node? Has there any reference changed?


I do understand what happens there. But I want to visualize the whole steps.

Let me clarify:

list and head are stored across the stack with reference and pointed to the same object. okay.

when it performs list.next = { val: c, next: null}, it updates the head like { val: 0, next: { val: 1, next: null }}.

Then list = list.next happens means now the list will be { val: 1, next: null }.

Consecutively next, the head is updated to { val: 0, next: { val: 1, next: {val: 2, next: null}}}.

At this point,

  1. how the things happen?
  2. is there any reference change?
  3. Why head variable is indicating the last node?
let head = { val: 0, next: null };

let list = head;

const arr = [1, 2, 3, 4];

for (let c of arr) {
  list.next = { val: c, next: null };
  list = list.next;
}

console.log(head);
  1. how the things happen?
  2. is there any reference change?
  3. Why head variable is indicating the last node?

Solution

  • The head is not getting updated to { val: 0, next: { val: 1, next: null }}. The head reference never changes, and keeps referencing the same node. To make head point to a different node, you'd have to make an assignment to head, but that never happens after the first and only assignment to it.

    On the other hand, list does get assigned new references, which is to the last node that was created.

    I want to visualize the whole steps.

    OK. After the first two statements, where head and list are assigned, we have the following state:

     head  list
      ↓     ↓
    ┌────────────┐
    │ value:   0 │
    │ next: null │
    └────────────┘
    

    The two variables head and list reference the same node.

    In the first iteration of the loop, c is 1, and the following property assignment is executed -- this is a mutation of the node that was created above:

    list.next = { val: c, next: null };
    

    The effect is as follows:

     head  list
      ↓     ↓
    ┌────────────┐   ┌────────────┐
    │ value:   0 │   │ value:   1 │
    │ next: ────────►│ next: null │
    └────────────┘   └────────────┘
    

    The next statement is crucial: the variable list gets a new reference. This is not a mutation, but a variable assignment:

    list = list.next;
    

    And so we get this:

     head              list
      ↓                 ↓
    ┌────────────┐   ┌────────────┐
    │ value:   0 │   │ value:   1 │
    │ next: ────────►│ next: null │
    └────────────┘   └────────────┘
    

    Notice how head and list no longer reference the same node!

    Let's do one more iteration of the loop, with c equal to 2. First:

    list.next = { val: c, next: null };
    

    ...leads to:

     head              list
      ↓                 ↓
    ┌────────────┐   ┌────────────┐   ┌────────────┐
    │ value:   0 │   │ value:   1 │   │ value:   2 │
    │ next: ────────►│ next: ────────►│ next: null │
    └────────────┘   └────────────┘   └────────────┘
    

    And again list = list.next; will assign only to the list variable, so we get:

     head                              list
      ↓                                 ↓
    ┌────────────┐   ┌────────────┐   ┌────────────┐
    │ value:   0 │   │ value:   1 │   │ value:   2 │
    │ next: ────────►│ next: ────────►│ next: null │
    └────────────┘   └────────────┘   └────────────┘
    

    The invariant here is that head always references the first node of the list, and at the start (and end) of a loop's iteration, list references the last node in the list.

    I hope this clarifies it.