DEV Community

Cover image for thank u, next: an introduction to linked lists
Ali Spittel
Ali Spittel

Posted on

thank u, next: an introduction to linked lists

In this post, we are going to be talking about the linked list data structure in the language of "thank u, next" by Ariana Grande. If you haven't watched the piece of art that is the music video for the song, please pause and do so before we begin.

Linked lists are linear collections of data that consist of nodes with data and pointers. We're going to be focusing on singly linked lists, which contain nodes that store the value of the node and a pointer to the next node. There are also other types of linked lists, like doubly linked lists and cyclical linked lists, but we'll focus on singly linked ones for now.

A couple quick definitions so we make sure we're on the same page:

  • A pointer stores the address of a value in memory. These can also point to nothing. A reference is similar, though can't point to nothing.
  • A Data Structure is a collection of data that can be implemented in any programming language.

We're going to be using the following linked list in this post:
linked list

In the above diagram, we see five different nodes, and each has a data value. The first four are in the order which she lists her exes:

Thought I'd end up with Sean
But he wasn't a match
Wrote some songs about Ricky
Now I listen and laugh
Even almost got married
And for Pete, I'm so thankful
Wish I could say, "Thank you" to Malcolm
'Cause he was an angel

The last one is Ari herself:

Plus, I met someone else
We havin' better discussions
I know they say I move on too fast
But this one gon' last
'Cause her name is Ari
And I'm so good with that (so good with that)

In addition to the data, each node stores a pointer to the next node. She always sings about her exes in the same order, and then herself last. When we iterate through a linked list, the same order will apply. We will start at the head node, which is the first one in the linked list, then move to the next one and so on. For the singly linked list, we won't move in reverse order or jump randomly from node to node, rather we'll go in the same order from head to the end.

We can create a super simple linked list by creating nodes and linking nodes in the following way:

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

let ari = new Node('Ari')
let malcolm = new Node('Malcolm', ari)
let pete = new Node('Pete', malcolm)
let ricky = new Node('Ricky', pete)
let sean = new Node('Sean', ricky)
Enter fullscreen mode Exit fullscreen mode

The final code for this post is also in Python here

If we print out what the Sean node looks like, we can see that it stores his name as the data attribute as well as a reference to the next node, which is Ricky. We can traverse all the nodes by using the next attribute!

Also, at the end of the linked list, there is a null pointer. In this case, since Ari is the queen, she's good by herself and doesn't need to move on to her next significant other. So, no thank u, next for her node.

Linked lists have some benefits compared to arrays, which are their main alternative in the world of linear data structures. Arrays are traditionally stored in a contiguous block in memory, which allows us to use the speedy indexing formula start_of_array_in_memory + space_allocated_for_each_array_item * index_of_item_we_want. While it's super efficient (O(1)) to get an item at an index, it's less efficient to insert or delete items from the array -- we would need to move everything to a different block in memory. It's not guaranteed that there's space before or after that array to insert the new item. If you insert or delete in the middle, the same logic applies -- you would have to move the items around in memory to fill holes or allocate more space.

Unlike arrays, linked lists do not need to be stored in one contiguous (or side to side 😉) block in memory which makes insertion and deletion at the beginning of the linked list easier. The pointers can point to any location in memory, so you don't have to move all the data around to add a new node.

That being said, if you are trying to search the linked list, insert to the middle, or delete from the middle of the linked list, the process will be much less efficient. We would need to traverse from the head to the node we are trying to access.

The other drawback with linked lists is that they use up a little more memory than arrays since they store the data and the pointer to the next node whereas arrays just store the data.

Let's look at the code we would use to implement some of these operations. We'll insert at the beginning of the linked list, and implement remove at index to show what needs to take place to do that:

class LinkedList {
  constructor() {
    // the head attribute stores a pointer to the first node in our linked list
    this.head = null
    this.length = 0
  }

  insert(data) {
    // inserts to the beginning of the linked list
    // what used to be  the head becomes the second element
    this.head = new Node(data, this.head) 
    this.length++
  }

  remove_value(value) {
    // remove any data value from the linked list

    // we need to store a pointer to a node and it's predecessor
    // so that when we remove the value we can just change the pointer!
    let prevNode = null
    let currentNode = this.head

    while (currentNode) {
      if (currentNode.data === value) {
        if (prevNode) {
          // Set the previous node's next value to the node we're deleting's next attribute
          // effectively removing it from our sequence
          prevNode.next = currentNode.next
        } else {
          this.head = currentNode.next
        }
        currentNode = null
        this.length--
        return true
      }
      // move to the next nodes
      prevNode = currentNode
      currentNode = currentNode.next
    }
  }
}

let thankUNext = new LinkedList()
thankUNext.insert('Ari')
thankUNext.insert('Malcolm')
thankUNext.insert('Pete')
thankUNext.insert('Ricky')
thankUNext.insert('Sean')

thankUNext.remove_value('Ricky')
Enter fullscreen mode Exit fullscreen mode

Here's a visualization of what it would look like to remove Ricky from our linked list in case Ari became less effing grateful for him:

Everything in red gets deleted.

Two other helpful methods are search and iterate:

iterate() {
  let node = this.head
  while (node) {
    console.log(node.data)
    node = node.next
  }
}

search(data) {
  let idx = 0
  let node = this.head
  while (node) {
    if (node.data === data) return idx
    node = node.next
    idx += 1
  }
  return -1
}
Enter fullscreen mode Exit fullscreen mode

So, we know that storing Ariana Grande's exes in a linked list is a great use of the data structure since we are always listing them in the same order when we sing along to "thank u, next", But what other data works well in a linked list? One use is a task queue. Printers, for example, can only print one thing out at a time, but we still want to load up future tasks and not have to press print for each page! When we create a list of tasks, we will always add the newest item to the end of the queue and then print out the one that's first in line! A back button implementation is similar! Or an undo hotkey! We will usually implement a stack or queue data structure on top of a linked list to implement these. I've also found them really helpful for a lot of code challenges.

Hopefully, this post taught you love instead of patience or pain.

Oldest comments (47)

Collapse
 
ben profile image
Ben Halpern

Collapse
 
rhymes profile image
rhymes

Maybe Pete reads dev.to

Collapse
 
ben profile image
Ben Halpern

Everybody's getting into the spirit

Collapse
 
ashwinv11 profile image
Ashwin Vaswani

If only there was an "I'm so effing grateful for my ex" 😄

Collapse
 
aspittel profile image
Ali Spittel

Oooh I should think of a place to put that!

Collapse
 
aspittel profile image
Ali Spittel

GOT IT

Collapse
 
ben profile image
Ben Halpern

Where do circular linked lists fit into this? 🤔

Thread Thread
 
aspittel profile image
Ali Spittel • Edited

Big Sean????

Thread Thread
 
ben profile image
Ben Halpern

I wasn't expecting such a relevant response.

Collapse
 
fpuffer profile image
Frank Puffer • Edited

That's a good introduction to linked lists.

Still I believe that linked lists are one of the most overrated data structures and that in practice there are very few use cases where they really perform better than dynamic arrays.

The main reason is that modern hardware has very efficient caches and that caching doesn't really work for linked lists.

While it's super efficient (O(1)) to get an item at an index, it's less efficient to insert or delete items from the array -- we would need to move everything to a different block in memory. It's not guaranteed that there's space before or after that array to insert the new item.

True, but caches are extremely good at moving blocks of memory around.

For a long time I believed myself that linked lists have huge advantages when inserting or deleting items and that this makes up for their difficult handling. But after doing some performance testing I did not find any practical case where they actually make sense.

At least I found that to be the case for C++ and Java. It might be different for certain interpreted languages where caching behaves differently.

Collapse
 
aspittel profile image
Ali Spittel

Agreed, especially since dynamic languages pre-allocate space so insertion and deletion is more efficient. They come in most handy for implementing stacks and queues IMO.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

One case where they do make sense, is sharing the same (potentially very long) tail among various heads. This is useful when you want to trace something that splits up a lot.

In certain edge-cases a combination of both may also be useful; that is, a linked list of arrays. I haven't come across any use case where this really makes sense though.

Collapse
 
lazarljubenovic profile image
Lazar Ljubenović

One thing that people forget so often is that "linked list" as data structure doesn't have to mean the exact in-memory representation. When you use a LinkedList class, you don't care for its implementation details: it could actually be storing elements sequentially like a traditional array, but exposing only the methods so you see it like a linked list.

When talking about data structures, it's all about the public interface.

Collapse
 
fpuffer profile image
Frank Puffer • Edited

When you use a LinkedList class, you don't care for its implementation details

But linked list is the name of a specific implementation of a list. When a class is named LinkedList, I expect it to be implemented this way.

If you don't care about the implementation, it would be better to declare and use an interface named List that is implemented by classes named LinkedList or ArrayList.

Thread Thread
 
lazarljubenovic profile image
Lazar Ljubenović

Indeed, I should've used the term Abstract Data Type (ADT), not data structure.

Still, I don't think that LinkedList refers to the way it's implemented. For example, it's not uncommon for a tree to be implemented as two arrays: one to hold the index of the parent and the other to store the actual data. Such data structure could still expose all public methods as a tree, and you wouldn't have to know how it's implemented.

Collapse
 
dean profile image
dean

The main reason I use linked lists are for queue structures where you frequently need to add to the end or pop the beginning, it's very bad if either of these operations are O(n). Instead you can use a simple singly-linked list.

When using a language like Java though, the LinkedList class isn't that good. The entire point of using a LinkedList over an ArrayList is that you can control certain things to make sure that the list operates as efficiently as possible, but Java's implementation doesn't allow for this.

Collapse
 
basedenergy profile image
Devin Golden

This is so good 😂

Collapse
 
rhymes profile image
rhymes

hahaha I loved it! Saying it's a unique way to talk about data structures is an understatement :D

Collapse
 
jonathansimonney profile image
Jonathan SIMONNEY

Tank you very much. Always love when I find an article talking precisely about something I have to do in a project.

Collapse
 
geoff profile image
Geoff Davis

Awesome! I'm going to have to check this pattern out, and see how Generators can be extended/written to create linked lists 🤔

Collapse
 
isabelcmdcosta profile image
Isabel Costa

This intersection of pop culture with tech it is so refreshing! This is a great post!

Collapse
 
alexkwonisawesome profile image
Hyeokwoo Alex Kwon

Whooa! This is an excellent article with a lot of "aris". I loved it.

I want to ask you if it is okay to translate your article in Korean and share it with my Korean dev community? I often translate awesome English articles into Korean. I really like to share yours too.

Thanks for the article anyways 🎉

Collapse
 
geraldosantos profile image
Geraldo dos Santos

Great way of explaining linked lists!

I wish this could give birth to a new series on dev.to:

Analogies to specific programming paradigms/concepts using songs

Collapse
 
aspittel profile image
Ali Spittel

I think I might actually do that with pop culture references! Think it's super fun!

Thank you!