Recommended Video Course
Working With Linked Lists in Python
Table of Contents
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Working With Linked Lists in Python
Linked lists are like a lesser-known cousin oflists. They’re not as popular or as cool, and you might not even remember them from your algorithms class. But in the right context, they can really shine.
In this article, you’ll learn:
collections.deque
for all of your linked list needsIf you’re looking to brush up on your coding skills for ajob interview, or if you want to learn more aboutPython data structures besides the usualdictionaries andlists, then you’ve come to the right place!
You can follow along with the examples in this tutorial by downloading the source code available at the link below:
Get the Source Code:Click here to get the source code you’ll use to learn about linked lists in this tutorial.
Linked lists are an ordered collection of objects. So what makes them different from normal lists? Linked lists differ from lists in the way that they store elements in memory. While lists use a contiguous memory block to store references to their data, linked lists store references as part of their own elements.
Before going more in depth on what linked lists are and how you can use them, you should first learn how they are structured. Each element of a linked list is called anode, and every node has two different fields:
Here’s what a typical node looks like:
A linked list is a collection of nodes. The first node is called thehead
, and it’s used as the starting point for any iteration through the list. The last node must have itsnext
reference pointing toNone
to determine the end of the list. Here’s how it looks:
Now that you know how a linked list is structured, you’re ready to look at some practical use cases for it.
Linked lists serve a variety of purposes in the real world. They can be used to implement (spoiler alert!) queues orstacks as well as graphs. They’re also useful for much more complex tasks, such as lifecycle management for an operating system application.
Queues and stacks differ only in the way elements are retrieved. For a queue, you use aFirst-In/First-Out (FIFO) approach. That means that the first element inserted in the list is the first one to be retrieved:
In the diagram above, you can see thefront andrear elements of the queue. When you append new elements to the queue, they’ll go to the rear end. When you retrieve elements, they’ll be taken from the front of the queue.
For a stack, you use aLast-In/First-Out (LIFO) approach, meaning that the last element inserted in the list is the first to be retrieved:
In the above diagram you can see that the first element inserted on the stack (index0
) is at the bottom, and the last element inserted is at the top. Since stacks use the LIFO approach, the last element inserted (at the top) will be the first to be retrieved.
Because of the way you insert and retrieve elements from the edges of queues and stacks, linked lists are one of the most convenient ways to implement these data structures. You’ll see examples of these implementations later in the article.
Graphs can be used to show relationships between objects or to represent different types of networks. For example, a visual representation of a graph—say a directed acyclic graph (DAG)—might look like this:
There are different ways to implement graphs like the above, but one of the most common is to use anadjacency list. An adjacency list is, in essence, a list of linked lists where each vertex of the graph is stored alongside a collection of connected vertices:
Vertex | Linked List of Vertices |
---|---|
1 | 2 → 3 → None |
2 | 4 → None |
3 | None |
4 | 5 → 6 → None |
5 | 6 → None |
6 | None |
In the table above, each vertex of your graph is listed in the left column. The right column contains a series of linked lists storing the other vertices connected with the corresponding vertex in the left column. This adjacency list could also be represented in code using adict
:
>>>graph={...1:[2,3,None],...2:[4,None],...3:[None],...4:[5,6,None],...5:[6,None],...6:[None]...}
The keys of this dictionary are the source vertices, and the value for each key is a list. This list is usually implemented as a linked list.
Note: In the above example you could avoid storing theNone
values, but we’ve retained them here for clarity and consistency with later examples.
In terms of both speed and memory, implementing graphs using adjacency lists is very efficient in comparison with, for example, anadjacency matrix. That’s why linked lists are so useful for graph implementation.
In most programming languages, there are clear differences in the way linked lists and arrays are stored in memory. In Python, however, lists aredynamic arrays. That means that the memory usage of both lists and linked lists is very similar.
Further reading: Python’simplementation of dynamic arrays is quite interesting and definitely worth reading about. Make sure to have a look and use that knowledge to stand out at your next company party!
Since the difference in memory usage between lists and linked lists is so insignificant, it’s better if you focus on their performance differences when it comes totime complexity.
In Python, you can insert elements into a list using.insert()
or.append()
. For removing elements from a list, you can use their counterparts:.remove()
and.pop()
.
The main difference between these methods is that you use.insert()
and.remove()
to insert or remove elements at a specific position in a list, but you use.append()
and.pop()
only to insert or remove elements at the end of a list.
Now, something you need to know about Python lists is that inserting or removing elements that arenot at the end of the list requires some element shifting in the background, making the operation more complex in terms of time spent. You can read the article mentioned above onhow lists are implemented in Python to better understand how the implementation of.insert()
,.remove()
,.append()
and.pop()
affects their performance.
With all this in mind, even though inserting elements at the end of a list using.append()
or.insert()
will have constant time,O(1), when you try inserting an element closer to or at the beginning of the list, the average time complexity will grow along with the size of the list:O(n).
Linked lists, on the other hand, are much more straightforward when it comes to insertion and deletion of elements at the beginning or end of a list, where their time complexity is always constant:O(1).
For this reason, linked lists have a performance advantage over normal lists when implementing a queue (FIFO), in which elements are continuously inserted and removed at the beginning of the list. But they perform similarly to a list when implementing a stack (LIFO), in which elements are inserted and removed at the end of the list.
When it comes to element lookup, lists perform much better than linked lists. When you know which element you want to access, lists can perform this operation inO(1) time. Trying to do the same with a linked list would takeO(n) because you need to traverse the whole list to find the element.
When searching for a specific element, however, both lists and linked lists perform very similarly, with a time complexity ofO(n). In both cases, you need to iterate through the entire list to find the element you’re looking for.
collections.deque
In Python, there’s a specific object in thecollections
module that you can use for linked lists calleddeque (pronounced “deck”), which stands fordouble-ended queue.
collections.deque
uses an implementation of a linked list in which you can access, insert, or remove elements from the beginning or end of a list with constantO(1) performance.
collections.deque
There are quite a fewmethods that come, by default, with adeque
object. However, in this article you’ll only touch on a few of them, mostly for adding or removing elements.
First, you need to create a linked list. You can use the following piece of code to do that withdeque
:
>>>fromcollectionsimportdeque>>>deque()deque([])
The code above will create an empty linked list. If you want to populate it at creation, then you can give it aniterable as input:
>>>deque(['a','b','c'])deque(['a', 'b', 'c'])>>>deque('abc')deque(['a', 'b', 'c'])>>>deque([{'data':'a'},{'data':'b'}])deque([{'data': 'a'}, {'data': 'b'}])
When initializing adeque
object, you can pass any iterable as an input, such as a string (also an iterable) or a list of objects.
Now that you know how to create adeque
object, you can interact with it by adding or removing elements. You can create anabcde
linked list and add a new elementf
like this:
>>>llist=deque("abcde")>>>llistdeque(['a', 'b', 'c', 'd', 'e'])>>>llist.append("f")>>>llistdeque(['a', 'b', 'c', 'd', 'e', 'f'])>>>llist.pop()'f'>>>llistdeque(['a', 'b', 'c', 'd', 'e'])
Bothappend()
andpop()
add or remove elements from the right side of the linked list. However, you can also usedeque
to quickly add or remove elements from the left side, orhead
, of the list:
>>>llist.appendleft("z")>>>llistdeque(['z', 'a', 'b', 'c', 'd', 'e'])>>>llist.popleft()'z'>>>llistdeque(['a', 'b', 'c', 'd', 'e'])
Adding or removing elements from both ends of the list is pretty straightforward using thedeque
object. Now you’re ready to learn how to usecollections.deque
to implement a queue or a stack.
As you learned above, the main difference between a queue and a stack is the way you retrieve elements from each. Next, you’ll find out how to usecollections.deque
to implement both data structures.
With queues, you want to add values to a list (enqueue
), and when the timing is right, you want to remove the element that has been on the list the longest (dequeue
). For example, imagine a queue at a trendy and fully booked restaurant. If you were trying to implement a fair system for seating guests, then you’d start by creating a queue and adding people as they arrive:
>>>fromcollectionsimportdeque>>>queue=deque()>>>queuedeque([])>>>queue.append("Mary")>>>queue.append("John")>>>queue.append("Susan")>>>queuedeque(['Mary', 'John', 'Susan'])
Now you have Mary, John, and Susan in the queue. Remember that since queues are FIFO, the first person who got into the queue should be the first to get out.
Now imagine some time goes by and a few tables become available. At this stage, you want to remove people from the queue in the correct order. This is how you would do that:
>>>queue.popleft()'Mary'>>>queuedeque(['John', 'Susan'])>>>queue.popleft()'John'>>>queuedeque(['Susan'])
Every time you callpopleft()
, you remove the head element from the linked list, mimicking a real-life queue.
What if you wanted tocreate a stack instead? Well, the idea is more or less the same as with the queue. The only difference is that the stack uses the LIFO approach, meaning that the last element to be inserted in the stack should be the first to be removed.
Imagine you’re creating a web browser’s history functionality in which store every page a user visits so they can go back in time easily. Assume these are the actions a random user takes on their browser:
If you’d like to map this behavior into a stack, then you could do something like this:
>>>fromcollectionsimportdeque>>>history=deque()>>>history.appendleft("https://realpython.com/")>>>history.appendleft("https://realpython.com/pandas-read-write-files/")>>>history.appendleft("https://realpython.com/python-csv/")>>>historydeque(['https://realpython.com/python-csv/', 'https://realpython.com/pandas-read-write-files/', 'https://realpython.com/'])
In this example, you created an emptyhistory
object, and every time the user visited a new site, you added it to yourhistory
variable usingappendleft()
. Doing so ensured that each new element was added to thehead
of the linked list.
Now suppose that after the user read both articles, they wanted to go back to the Real Python home page to pick a new article to read. Knowing that you have a stack and want to remove elements using LIFO, you could do the following:
>>>history.popleft()'https://realpython.com/python-csv/'>>>history.popleft()'https://realpython.com/pandas-read-write-files/'>>>historydeque(['https://realpython.com/'])
There you go! Usingpopleft()
, you removed elements from thehead
of the linked list until you reached the Real Python home page.
From the examples above, you can see how useful it can be to havecollections.deque
in your toolbox, so make sure to use it the next time you have a queue- or stack-based challenge to solve.
Now that you know how to usecollections.deque
for handling linked lists, you might be wondering why you would ever implement your own linked list in Python. There are a few reasons to do it:
Feel free to skip this next section if you’re not interested in any of the above, or if you already aced implementing your own linked list in Python. Otherwise, it’s time to implement some linked lists!
First things first, create aclass to represent your linked list:
classLinkedList:def__init__(self):self.head=None
The only information you need to store for a linked list is where the list starts (thehead
of the list). Next, create another class to represent each node of the linked list:
classNode:def__init__(self,data):self.data=dataself.next=None
In the above class definition, you can see the two main elements of every single node:data
andnext
. You can also add a__repr__
to both classes to have a more helpful representation of the objects:
classNode:def__init__(self,data):self.data=dataself.next=Nonedef__repr__(self):returnself.dataclassLinkedList:def__init__(self):self.head=Nonedef__repr__(self):node=self.headnodes=[]whilenodeisnotNone:nodes.append(node.data)node=node.nextnodes.append("None")return" -> ".join(nodes)
Have a look at an example of using the above classes to quickly create a linked list with three nodes:
>>>llist=LinkedList()>>>llistNone>>>first_node=Node("a")>>>llist.head=first_node>>>llista -> None>>>second_node=Node("b")>>>third_node=Node("c")>>>first_node.next=second_node>>>second_node.next=third_node>>>llista -> b -> c -> None
By defining a node’sdata
andnext
values, you can create a linked list quite quickly. TheseLinkedList
andNode
classes are the starting points for our implementation. From now on, it’s all about increasing their functionality.
Here’s a slight change to the linked list’s__init__()
that allows you to quickly create linked lists with some data:
def__init__(self,nodes=None):self.head=NoneifnodesisnotNone:node=Node(data=nodes.pop(0))self.head=nodeforeleminnodes:node.next=Node(data=elem)node=node.next
With the above modification, creating linked lists to use in the examples below will be much faster.
One of the most common things you will do with a linked list is totraverse it. Traversing means going through every single node, starting with thehead
of the linked list and ending on the node that has anext
value ofNone
.
Traversing is just a fancier way to say iterating. So, with that in mind, create an__iter__
to add the same behavior to linked lists that you would expect from a normal list:
def__iter__(self):node=self.headwhilenodeisnotNone:yieldnodenode=node.next
The method above goes through the list andyields every single node. The most important thing to remember about this__iter__
is that you need to always validate that the currentnode
is notNone
. When that condition isTrue
, it means you’ve reached the end of your linked list.
After yielding the current node, you want to move to the next node on the list. That’s why you addnode = node.next
. Here’s an example of traversing a random list and printing each node:
>>>llist=LinkedList(["a","b","c","d","e"])>>>llista -> b -> c -> d -> e -> None>>>fornodeinllist:...print(node)abcde
In other articles, you might see the traversing defined into a specific method calledtraverse()
. However, using Python’sbuilt-in methods to achieve said behavior makes this linked list implementation a bit morePythonic.
There are different ways to insert new nodes into a linked list, each with its own implementation and level of complexity. That’s why you’ll see them split into specific methods for inserting at the beginning, end, or between nodes of a list.
Inserting a new node at the beginning of a list is probably the most straightforward insertion since you don’t have to traverse the whole list to do it. It’s all about creating a new node and then pointing thehead
of the list to it.
Have a look at the following implementation ofadd_first()
for the classLinkedList
:
defadd_first(self,node):node.next=self.headself.head=node
In the above example, you’re settingself.head
as thenext
reference of the new node so that the new node points to the oldself.head
. After that, you need to state that the newhead
of the list is the inserted node.
Here’s how it behaves with a sample list:
>>>llist=LinkedList()>>>llistNone>>>llist.add_first(Node("b"))>>>llistb -> None>>>llist.add_first(Node("a"))>>>llista -> b -> None
As you can see,add_first()
always adds the node to thehead
of the list, even if the list was empty before.
Inserting a new node at the end of the list forces you to traverse the whole linked list first and to add the new node when you reach the end. You can’t just append to the end as you would with a normal list because in a linked list you don’t know which node is last.
Here’s an example implementation of a function for inserting a node to the end of a linked list:
defadd_last(self,node):ifself.headisNone:self.head=nodereturnforcurrent_nodeinself:passcurrent_node.next=node
First, you want to traverse the whole list until you reach the end (that is, until thefor
loop raises aStopIteration
exception). Next, you want to set thecurrent_node
as the last node on the list. Finally, you want to add the new node as thenext
value of thatcurrent_node
.
Here’s an example ofadd_last()
in action:
>>>llist=LinkedList(["a","b","c","d"])>>>llista -> b -> c -> d -> None>>>llist.add_last(Node("e"))>>>llista -> b -> c -> d -> e -> None>>>llist.add_last(Node("f"))>>>llista -> b -> c -> d -> e -> f -> None
In the code above, you start by creating a list with four values (a
,b
,c
, andd
). Then, when you add new nodes usingadd_last()
, you can see that the nodes are always appended to the end of the list.
Inserting between two nodes adds yet another layer of complexity to the linked list’s already complex insertions because there are two different approaches that you can use:
It might seem weird to split these into two methods, but linked lists behave differently than normal lists, and you need a different implementation for each case.
Here’s a method that adds a nodeafter an existing node with a specific data value:
defadd_after(self,target_node_data,new_node):ifself.headisNone:raiseException("List is empty")fornodeinself:ifnode.data==target_node_data:new_node.next=node.nextnode.next=new_nodereturnraiseException("Node with data '%s' not found"%target_node_data)
In the above code, you’re traversing the linked list looking for the node with data indicating where you want to insert a new node. When you find the node you’re looking for, you’ll insert the new node immediately after it and rewire thenext
reference to maintain the consistency of the list.
The only exceptions are if the list is empty, making it impossible to insert a new node after an existing node, or if the list does not contain the value you’re searching for. Here are a few examples of howadd_after()
behaves:
>>>llist=LinkedList()>>>llist.add_after("a",Node("b"))Exception: List is empty>>>llist=LinkedList(["a","b","c","d"])>>>llista -> b -> c -> d -> None>>>llist.add_after("c",Node("cc"))>>>llista -> b -> c -> cc -> d -> None>>>llist.add_after("f",Node("g"))Exception: Node with data 'f' not found
Trying to useadd_after()
on an empty list results in anexception. The same happens when you try to add after a nonexistent node. Everything else works as expected.
Now, if you want to implementadd_before()
, then it will look something like this:
1defadd_before(self,target_node_data,new_node): 2ifself.headisNone: 3raiseException("List is empty") 4 5ifself.head.data==target_node_data: 6returnself.add_first(new_node) 7 8prev_node=self.head 9fornodeinself:10ifnode.data==target_node_data:11prev_node.next=new_node12new_node.next=node13return14prev_node=node1516raiseException("Node with data '%s' not found"%target_node_data)
There are a few things to keep in mind while implementing the above. First, as withadd_after()
, you want to make sure to raise an exception if the linked list is empty (line 2) or the node you’re looking for is not present (line 16).
Second, if you’re trying to add a new node before the head of the list (line 5), then you can reuseadd_first()
because the node you’re inserting will be the newhead
of the list.
Finally, for any other case (line 9), you should keep track of the last-checked node using theprev_node
variable. Then, when you find the target node, you can use thatprev_node
variable to rewire thenext
values.
Once again, an example is worth a thousand words:
>>>llist=LinkedList()>>>llist.add_before("a",Node("a"))Exception: List is empty>>>llist=LinkedList(["b","c"])>>>llistb -> c -> None>>>llist.add_before("b",Node("a"))>>>llista -> b -> c -> None>>>llist.add_before("b",Node("aa"))>>>llist.add_before("c",Node("bb"))>>>llista -> aa -> b -> bb -> c -> None>>>llist.add_before("n",Node("m"))Exception: Node with data 'n' not found
Withadd_before()
, you now have all the methods you need to insert nodes anywhere you’d like in your list.
To remove a node from a linked list, you first need to traverse the list until you find the node you want to remove. Once you find the target, you want to link its previous and next nodes. This re-linking is what removes the target node from the list.
That means you need to keep track of the previous node as you traverse the list. Have a look at an example implementation:
1defremove_node(self,target_node_data): 2ifself.headisNone: 3raiseException("List is empty") 4 5ifself.head.data==target_node_data: 6self.head=self.head.next 7return 8 9previous_node=self.head10fornodeinself:11ifnode.data==target_node_data:12previous_node.next=node.next13return14previous_node=node1516raiseException("Node with data '%s' not found"%target_node_data)
In the above code, you first check that your list is not empty (line 2). If it is, then you raise an exception. After that, you check if the node to be removed is the currenthead
of the list (line 5). If it is, then you want the next node in the list to become the newhead
.
If none of the above happens, then you start traversing the list looking for the node to be removed (line 10). If you find it, then you need to update its previous node to point to its next node, automatically removing the found node from the list. Finally, if you traverse the whole list without finding the node to be removed (line 16), then you raise an exception.
Notice how in the above code you useprevious_node
to keep track of the, well, previous node. Doing so ensures that the whole process will be much more straightforward when you find the right node to be deleted.
Here’s an example using a list:
>>>llist=LinkedList()>>>llist.remove_node("a")Exception: List is empty>>>llist=LinkedList(["a","b","c","d","e"])>>>llista -> b -> c -> d -> e -> None>>>llist.remove_node("a")>>>llistb -> c -> d -> e -> None>>>llist.remove_node("e")>>>llistb -> c -> d -> None>>>llist.remove_node("c")>>>llistb -> d -> None>>>llist.remove_node("a")Exception: Node with data 'a' not found
That’s it! You now know how to implement a linked list and all of the main methods for traversing, inserting, and removing nodes. If you feel comfortable with what you’ve learned and you’re craving more, then feel free to pick one of the challenges below:
get(i)
or evenllist[i]
.llist.reverse()
.Queue()
object inheriting this article’s linked list withenqueue()
anddequeue()
methods.Apart from being great practice, doing some extra challenges on your own is an effective way to assimilate all the knowledge you’ve gained. If you want to get a head start by reusing all the source code from this article, then you can download everything you need at the link below:
Get the Source Code:Click here to get the source code you’ll use to learn about linked lists in this tutorial.
Until now, you’ve been learning about a specific type of linked list calledsingly linked lists. But there are more types of linked lists that can be used for slightly different purposes.
Doubly linked lists are different from singly linked lists in that they have two references:
previous
field references the previous node.next
field references the next node.The end result looks like this:
If you wanted to implement the above, then you could make some changes to your existingNode
class in order to include aprevious
field:
classNode:def__init__(self,data):self.data=dataself.next=Noneself.previous=None
This kind of implementation would allow you to traverse a list in both directions instead of only traversing usingnext
. You could usenext
to go forward andprevious
to go backward.
In terms of structure, this is how a doubly linked list would look:
You learned earlier thatcollections.deque
uses a linked list as part of its data structure. This is the kind oflinked list it uses. With doubly linked lists,deque
is capable of inserting or deleting elements from both ends of a queue with constantO(1) performance.
Circular linked lists are a type of linked list in which the last node points back to thehead
of the list instead of pointing toNone
. This is what makes them circular. Circular linked lists have quite a few interesting use cases:
This is what a circular linked list looks like:
One of the advantages of circular linked lists is that you can traverse the whole list starting at any node. Since the last node points to thehead
of the list, you need to make sure that you stop traversing when you reach the starting point. Otherwise, you’ll end up in an infinite loop.
In terms of implementation, circular linked lists are very similar to singly linked list. The only difference is that you can define the starting point when you traverse the list:
classCircularLinkedList:def__init__(self):self.head=Nonedeftraverse(self,starting_point=None):ifstarting_pointisNone:starting_point=self.headnode=starting_pointwhilenodeisnotNoneand(node.next!=starting_point):yieldnodenode=node.nextyieldnodedefprint_list(self,starting_point=None):nodes=[]fornodeinself.traverse(starting_point):nodes.append(str(node))print(" -> ".join(nodes))
Traversing the list now receives an additional argument,starting_point
, that is used to define the start and (because the list is circular) the end of the iteration process. Apart from that, much of the code is the same as what we had in ourLinkedList
class.
To wrap up with a final example, have a look at how this new type of list behaves when you give it some data:
>>>circular_llist=CircularLinkedList()>>>circular_llist.print_list()None>>>a=Node("a")>>>b=Node("b")>>>c=Node("c")>>>d=Node("d")>>>a.next=b>>>b.next=c>>>c.next=d>>>d.next=a>>>circular_llist.head=a>>>circular_llist.print_list()a -> b -> c -> d>>>circular_llist.print_list(b)b -> c -> d -> a>>>circular_llist.print_list(d)d -> a -> b -> c
There you have it! You’ll notice that you no longer have theNone
while traversing the list. That’s because there is no specific end to a circular list. You can also see that choosing different starting nodes will render slightly different representations of the same list.
In this article, you learned quite a few things! The most important are:
collections.deque
to implement queues and stacksIf you want to learn more about linked lists, then check outVaidehi Joshi’s Medium post for a nice visual explanation. If you’re interested in a more in-depth guide, then theWikipedia article is quite thorough. Finally, if you’re curious about the reasoning behind the current implementation ofcollections.deque
, then check outRaymond Hettinger’s thread.
You can download the source code used throughout this tutorial by clicking on the following link:
Get the Source Code:Click here to get the source code you’ll use to learn about linked lists in this tutorial.
Feel free to leave any questions or comments below. Happy Pythoning!
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Working With Linked Lists in Python
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.
AboutPedro Pregueiro
Hi! My name is Pedro and I'm a Python developer who loves coding, burgers and playing guitar.
» More about PedroMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Keep Learning
Already have an account?Sign-In
Almost there! Complete this form and click the button below to gain instant access:
Linked Lists (Source Code)