Singly Linked List 'Push' Method for Dummies (me)

Nathan G Bornstein - Jan 4 - - Dev Community

Whenever I come back to implementing the most basic method for singly linked lists (push), I'm always scratching my head, thinking to myself, "how did I implement that again?". So here I am once again my friends, typing out concepts I don't fully understand in order to make sense of them.


Linked lists at their core are simply alternatives to using a basic array. Why might we choose a linked list over an array, you ask? Let's go over the benefits:

1.) More efficient insertion/deletion processes (typically an O(1) time complexity) over standard arrays

2.) Useful for datasets where the exact number of items isn't known; this makes for a much more flexible data structure that can be changed more efficiently

3.) Serialization (conversion from an object into bytes for transfer) is more efficient with linked lists than arrays


As with all data structures however, there are downsides to using them and linked lists are no exception. When might we NOT want to use a linked list? Let's go over the drawbacks:

1.) If your dataset requires a lot of data inquiries (look-ups), linked lists will perform worse than arrays. This is due to the fact that their location in memory (e.g., an index position) doesn't typically exist (but it can; the efficiency of that look-up will still perform worse than a basic array though)

2.) Smaller datasets won't necessarily benefit from any of the reasons for using a linked list. It may be more useful to simply use an array if the cost/benefit in time and space complexity for insertions/deletions won't be a cause for concern


So in summary for its use-cases:

  • Linked lists are good for when you have a large dataset and it will mainly be used for insertion/deletion of data

And for its drawbacks:

  • It probably won't be of much use if you have a small dataset that will mainly be used for searches and data-manipulation

Alrighty, let's dive into implementing the basic structure of a linked list and its most basic push method:


JavaScript code snippet for basic linked list structure


We first have our two constructor functions that will be the backbone in building our linked list; the LL will be where we have our head, tail and length properties.

The head is our entry point into the linked list. All data will be passed through the head in order to populate our list.

The tail is our terminal point for the linked list. The tail will always contain the very last data point in our list. We don't necessarily need to have a tail in a singly linked list, but it does make it more efficient with finding the very end of the list.

The length property is simply what it sounds like. It'll let us know how many items are contained within the list.

As for the Node constructor, its sole purpose is to add onto the linked list. It will contain some data (val) and a next pointer that will be linked to the subsequent node in the list. The default value for next will be null to accommodate any future node insertions.


Now let's explore our first method:

PUSH


code snippet for linked list push method


If you're seeing LL.prototype.push and are thinking "the heck is a prototype?", this is a great resource. It's a bit lengthy, but I can almost guarantee it will cement the concept for you.

And if you're wondering why I'm not using ES6+ class syntax, it's because I'm exercising my constructor/prototype muscles, as they're getting flabby.

ANYWHO

What we're trying to accomplish with the push method is very similar to the built-in array method by the same name in JavaScript; we simply want to add some data to the end of our list. This is first accomplished by assigning the desired piece of data (or node) to a new instance of our Node constructor we defined previously:
node instance snippet for linked list

After that, the first thing we want to check for is if we even have anything assigned to our head property, which as you remember, is the entry point into our linked list. If we don't have anything assigned to head, then we'll want to populate it first, as well as assigning that same piece of data to our tail property:
conditional statement to check if our linked list's head it established
The reason we want to assign this.tail to this.head is because any new piece of data (newNode) that we are inserting into the end of our list will need to mirror our tail. This will become more apparent once we dive further into this method. Bare with me, I know it's confusing as heck the first few (or dozen+) times you're trying to make sense of this.


So now that we have our node to insert and we've checked our head to see if it's empty, let's see what we do once our head is actually occupied:
else statement to populate our list if the head is already occupied

This is a duplicate assignment we're doing for two different properties. this.tail.next is being populated by the Node constructor with what we've assigned to our constant variable newNode.

The reason our LL and Node constructors are linked is thanks to the previous line of code we performed:
conditional statement to check if our linked list's head it established


this.tail.next is essentially just this.head.next


Read that again.

And then again.

And then look at the code and then read it again.

⁽ˢᵉʳᶦᵒᵘˢˡʸ ᵖˡᵉᵃˢᵉ ᵈᵒ ᶦᵗ⁾


That previous point is essentially the crux of how this is all working. We have our Node constructor which will be assigned to the this.head and this.tail properties in our LL constructor and all future data will be passed through both of those properties once our initial head is populated.

Once we've arrived to our third push invocation of data insertion, we'll only be working from the tail from that point on. The head exists to initially populate our list and then act as a liaison to transfer its population onto the proceeding tail properties.


Again, to reiterate, once we've populated this.head and assigned this.tail to this.head, we're simply populating the tail and the inherent next property that the tail has with any subsequent data.


If that's not fully making sense, don't worry. Even upon taking my time and typing all of this out, I'm having to second-guess what I'm typing out. This are very unintuitive concepts.

BUT if you were able to make sense of that, congratulations! Either you knew about that already or I, by some miraculous intervention, clarified that a little for you. All that's left to do is return out from the method and increment the this.length property on the LL constructor to signify that we've populated our list:

Image description

Some implementations of the push method have multiple sections where they increase the this.length property, but I like this rendition as it adheres to the fabled DRY principle.


And that's all I have to give, friends. You can bet your bottom dollar I'll be back with the 𝓈𝓅𝑜𝑜𝓀𝓎 pop method for linked lists when I get around to it.

~ Thanks for stoppin' by! ~

<3<3

. . . . . . . . . . . . .