Search code examples
javascriptclassdata-structuressingly-linked-list

How does push method work on head Node in SinglyLinkedList


I can't wrap my head around how does Head also changes whenever assigning a new Node to Tail. Do I miss something important about how Class works? Please explain in detail to me.

 class Node {
    constructor(val){
        this.val = val
        this.next = null
    }
 }

class SinglyLinkedList {
    constructor(){
        this.head = null
        this.tail = null
        this.length = 0
    }
    push(val){
        let newNode = new Node(val)
        if(!this.head){
            this.head = newNode 
            this.tail = this.head
        }else{
            this.tail.next = newNode // <-- this line
            this.tail = newNode
        }
        this.length++
        return this
    }
}
 let list = new SinglyLinkedList()
 slist.push(1)
 slist.push(2)
 slist.push(3)

 console.log(JSON.stringify(list))
//output {"head":{"val":1,"next":{"val":2,"next":
// {"val":3,"next":null}}},"tail":{"val":3,"next":null},"length":3}

Doesn't Head only get the first Node object?


Solution

  • It might help to visualise this.

    When let list = new SinglyLinkedList() has executed we can picture the state like this:

    slist
      │
      ▼
    ┌────────────┐
    │ head: null │
    │ tail: null │
    │ length: 0  │
    └────────────┘           
    

    Now let's see what happens when slist.push(1) is called. While executing that method, this will be slist. First newNode is created as new Node(val):

    this
    slist             newNode
      │                 │
      ▼                 ▼
    ┌────────────┐    ┌────────────┐
    │ head: null │    │ val:  1    │
    │ tail: null │    │ next: null │
    │ length: 0  │    └────────────┘
    └────────────┘
    

    As this.head is null, !this.head is a truthy expression, so the if block gets executed. this.head = newNode results in this state:

    this
    slist             newNode
      │                 │
      ▼                 ▼
    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │
    │ tail: null │    │ next: null │
    │ length: 0  │    └────────────┘
    └────────────┘
    

    After this.tail = this.head executes, we get this:

    this
    slist             newNode
      │                 │
      ▼                 ▼
    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │
    │ tail: ────────► │ next: null │
    │ length: 0  │    └────────────┘
    └────────────┘
    

    And finally, the length property is incremented. return this is executed, but that return value is not used by the caller. So the caller sees this:

    slist (returned)
      │
      ▼
    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │
    │ tail: ────────► │ next: null │
    │ length: 1  │    └────────────┘
    └────────────┘
    

    With the next call of push, we again get a newNode:

    this
    slist                               newNode
      │                                   │
      ▼                                   ▼
    ┌────────────┐    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │    │ val:  2    │
    │ tail: ────────► │ next: null │    │ next: null │
    │ length: 1  │    └────────────┘    └────────────┘
    └────────────┘
    

    This time head is not null and so we get into the else block. There we execute this.tail.next = newNode, which is what you asked about. this references the list object (the first box in the diagram), this.tail references a node object (the second box in the diagram), and this.tail.next is the property that is currently null but gets a new reference via the assignment. After the assignment that next property references newNode:

    this
    slist                               newNode
      │                                   │
      ▼                                   ▼
    ┌────────────┐    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │    │ val:  2    │
    │ tail: ────────► │ next: ────────► │ next: null │
    │ length: 1  │    └────────────┘    └────────────┘
    └────────────┘
    

    And finally this.tail = newNode and this.length++ are executed:

    this
    slist                               newNode
      │                                   │
      ▼                                   ▼
    ┌────────────┐    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │    │ val:  2    │
    │ tail: ───────┐  │ next: ────────► │ next: null │
    │ length: 2  │ │  └────────────┘    └────────────┘
    └────────────┘ │                      ▲
                   └──────────────────────┘
    

    Repeat this process for the call of slist.push(3) and the caller ends up with:

    slist
      │
      ▼
    ┌────────────┐    ┌────────────┐    ┌────────────┐    ┌────────────┐
    │ head: ────────► │ val:  1    │    │ val:  2    │    │ val:  3    │
    │ tail: ───────┐  │ next: ────────► │ next: ────────► │ next: null │
    │ length: 2  │ │  └────────────┘    └────────────┘    └────────────┘
    └────────────┘ │                                         ▲
                   └─────────────────────────────────────────┘