Search code examples
javascriptclassooplinked-listthis

Why does this.tail change the property of this.head in my Linkedlist class?


Consider a LinkedList class which mimics the Linkedlist data structure as below:

class LinkedList {
  constructor(value) {
    this.head = {
      value: value,
      next: null
    };
    this.tail = this.head;
    this.length = 1;
  }
  append(value) {
    const newNode = {
      value: value,
      next: null
    }
    this.tail.next = newNode; // why does this change head.next ?
    this.tail = newNode;
    this.length++;
    return this;
  }
}

let myLinkedList = new LinkedList(10);
myLinkedList.append(5);

log output

LinkedList {
  head: { value: 10, next: { value: 5, next: null } },
  tail: { value: 5, next: null },
  length: 2
}

I see that this.tail.next will change the next property of tail as well (then this.tail = newNode will reassign tail to newNode). What I don't understand here is why this.tail.next would also change the next property of this.head?

Also, when appending another number to the list myLinkedList.append(16), it keeps updating the next property of head as below:

LinkedList {
  head: { value: 10, next: { value: 5, next: [Object] } },
  tail: { value: 16, next: null },
  length: 3
}

Maybe a possible reason relates to the constructor where I define this.tail = this.head? But I'm not really sure since this one only assigns value of head to tail.

To sum up, my question is why does this.tail.next = newNode change the next property of the head? Also, when appending another value, why does it change head.next.next and so on?


Solution

  • When the constructor has run, this.tail and this.head reference the same object, so any assignment you make to this.tail.next is visible in this.head, since that really is a reference to the same object that is being mutated.

    It may help to visualise this. Once the constructor has run, we have this situation:

         this.head
          ↓          
        ┌───────────┐
        │ value: 10 │
        │ next: null│
        └───────────┘
          ↑
         this.tail
    

    Then append(5) will first create a new node:

         this.head        newNode
          ↓                ↓
        ┌───────────┐    ┌───────────┐
        │ value: 10 │    │ value: 5  │
        │ next:null │    │ next:null │
        └───────────┘    └───────────┘
          ↑
         this.tail
    

    Then this.tail.next = newNode; is executed, which is a modification of that next property in the first object:

         this.head        newNode
          ↓                ↓
        ┌───────────┐    ┌───────────┐
        │ value: 10 │    │ value: 5  │
        │ next: ———————→ │ next:null │
        └───────────┘    └───────────┘
          ↑
         this.tail
    

    So indeed, this also changes this.head.next... because it is just the very same property.

    Then this.tail = newNode; is executed:

         this.head        newNode
          ↓                ↓
        ┌───────────┐    ┌───────────┐
        │ value: 10 │    │ value: 5  │
        │ next: ———————→ │ next:null │
        └───────────┘    └───────────┘
                           ↑
                          this.tail
    

    The next time append is called, the next property of the second object will be updated, and so we get:

         this.head                         newNode
          ↓                                 ↓
        ┌───────────┐    ┌───────────┐    ┌───────────┐
        │ value: 10 │    │ value: 5  │    │ value: 16 │
        │ next: ———————→ │ next: ———————→ │ next:null │
        └───────────┘    └───────────┘    └───────────┘
                                            ↑
                                           this.tail
    

    And yes, this change is also traceable from this.head, because... it is a linked list... so it should be traceable. As each next property refers to the next node, you can find your way from head to any of the nodes.