Hello! Welcome to our next lesson.
In the last session, we explored dynamic arrays and introduced amortized analysis to understand why their append operation is so efficient in practice, despite occasional costly resizes. We concluded by highlighting a key weakness: inserting or deleting elements in the middle of a dynamic array is an expensive O(N) operation due to the need to shift subsequent elements.
Today, we'll tackle that exact problem by studying the linked list. This fundamental data structure abandons the requirement of contiguous memory, giving us a completely different set of performance trade-offs. By the end of this lesson, you will be able to implement both singly and doubly linked lists and, crucially, analyze when to use them instead of an array. This is a classic architectural decision that appears in many forms across software engineering.
1. What is a Linked List?
Unlike an array, which stores its elements side-by-side in a single block of memory, a linked list is a collection of individual objects called nodes that can be scattered anywhere in memory. Each node contains two key pieces of information:
- The actual data (the value we want to store).
- A pointer (or reference) to the next node in the sequence.
The list is formed by "chaining" these nodes together. We keep track of the start of the list with a single reference, typically called the head. The last node in the chain points to null (or None in Python) to signify the end.

To get a dynamic feel for this structure, let's watch a short introductory video.
Linked Lists - Singly & Doubly Linked - DSA Course in Python Lecture 3
The video 'Linked Lists - Singly & Doubly Linked' by Greg Hogg provides a very clear, animated introduction to the concept of a singly linked list.
Please watch the first 2 minutes and 43 seconds (from 00:00 to 02:43). Focus on understanding: The structure of a Node (value and next pointer). How nodes are linked together to form a chain. The fundamental difference in memory allocation compared to an array.
2. Operations and Performance Trade-offs
The non-contiguous nature of linked lists leads to a unique performance profile. Let's analyze the common operations.
Access and Search
To access an element at a specific index (e.g., the 5th element), you have no choice but to start at the head and follow the next pointers one by one until you reach the desired position. This is a linear traversal, making both access by index and searching for a value an O(N) operation.
This is the most significant drawback of a linked list compared to a dynamic array, which offers O(1) indexed access.
Insertion and Deletion
Here's where linked lists shine.
- At the Head: Adding or removing the first element is incredibly efficient. To add a new head, you create a new node, point its
nextreference to the oldhead, and then update the list'sheadto be your new node. This is a constant number of pointer changes, making it anO(1)operation. - In the Middle: If you already have a pointer to the node before your desired insertion/deletion point, you can perform the operation in
O(1)time by simply rearranging thenextpointers. For example, to deleteNode Bwhich is afterNode A, you just changeNode A'snextpointer to point toNode B'snextpointer. No shifting of elements is required!
However, it's important to remember that finding the spot to insert/delete is still an O(N) operation. The O(1) benefit only applies once you're at the correct location.
Let's continue with the video to see these operations explained.
Linked Lists - Singly & Doubly Linked - DSA Course in Python Lecture 3
This segment of the same video explains the time complexity of common linked list operations.
Watch from 02:43 to 06:42. Pay close attention to the reasoning behind the time complexity for accessing, adding, and deleting nodes.
The Impact of Cache Locality
There's a more subtle performance factor that's crucial for an architectural mindset: cache locality. Modern CPUs have small, extremely fast memory caches. When the CPU requests data from a memory address, it also fetches nearby memory into the cache, assuming it might need it soon. This is called spatial locality.
- Arrays are cache-friendly. Because their elements are contiguous, traversing an array often results in cache hits, as the next elements are likely already in the cache.
- Linked Lists are cache-unfriendly. Since their nodes are scattered across memory, traversing a linked list can lead to frequent cache misses. Each time you follow a
nextpointer, the CPU may have to make a slow trip to main memory (RAM) to fetch the next node.
To understand this critical low-level difference, the following video provides an excellent explanation.
The video 'ARRAYLIST VS LINKEDLIST' by Core Dumped explains the concept of CPU caching and why arrays are much more cache-friendly than linked lists.
Watch from 08:09 to 09:40. Focus on the concepts of cache hits, cache misses, and why the contiguous memory of arrays gives them a significant performance advantage in this regard.
3. Doubly Linked Lists
A singly linked list only allows forward traversal. What if you need to go backward? And what if you need to delete a node but you only have a reference to that specific node, not the one before it?
A doubly linked list solves this by adding a second pointer to each node: prev (previous).
- Each node now contains:
data, anextpointer, and aprevpointer. - The
head'sprevpointer isnull. - The
tail'snextpointer isnull.
This design gives us two major advantages:
- Bidirectional Traversal: You can move forward or backward through the list from any node.
- Efficient Deletion: If you have a reference to a node you want to delete, you can do it in
O(1)time. You can access itsprevandnextnodes directly to update their pointers, effectively cutting the node out of the chain.
The trade-off is the extra memory required for the prev pointer in every node.
Let's watch a brief overview of the doubly linked list structure.
Linked Lists - Singly & Doubly Linked - DSA Course in Python Lecture 3
We return to Greg Hogg's video for a quick explanation of the doubly linked list structure and its benefits.
Watch from 06:42 to 08:39. Notice how the prev pointer allows for O(1) deletion of a node given only a reference to that node itself.
4. Implementation in Practice
The best way to solidify your understanding is to see how a linked list is implemented. Since you have a web development background, we'll use Python, whose clean syntax lets us focus on the data structure's logic.
A linked list implementation typically starts with a Node class. For a singly linked list, it looks like this:
class Node:
def __init__(self, value):
self.value = value
self.next = None
The LinkedList class itself would then manage the head (and often a tail for efficiency) and contain methods for operations like append, prepend, delete, and search. The core logic for most operations involves a while loop that traverses the list:
# A common traversal pattern
current = self.head
while current is not None:
# do something with current.value
current = current.next
The following video walks through a full implementation of both singly and doubly linked lists.
Linked Lists - Singly & Doubly Linked - DSA Course in Python Lecture 3
This final segment of the video provides a complete, practical coding demonstration in Python.
Watch from 08:39 to the end (16:59). You don't need to code along right now, but focus on: The structure of the SinglyNode and DoublyNode classes. How nodes are manually connected after creation. The logic inside the traverse and search functions, which exemplifies the core loop pattern. How O(1) insertion is achieved in a doubly linked list by managing both head and tail pointers.
5. Summary of Trade-offs: Array vs. Linked List
Let's consolidate the key trade-offs. This is the "architectural mindset" in action: knowing which tool to use for which job based on its performance characteristics.
| Operation | Dynamic Array | Linked List (Singly/Doubly) | Why? |
|---|---|---|---|
| Access by Index | O(1) |
O(N) |
Arrays use pointer arithmetic from a base address; linked lists must be traversed from the head. |
| Search by Value | O(N) |
O(N) |
In the worst case, both require scanning the entire structure. |
| Insertion at Head | O(N) |
O(1) |
Arrays require shifting all elements; linked lists only need to update the head pointer. |
| Insertion at Tail | O(1) (amortized) |
O(1) (if tail pointer is kept) |
Dynamic arrays may resize; linked lists just update the tail. |
| Insertion in Middle | O(N) |
O(N) (find) + O(1) (insert) |
Arrays must shift elements. Linked lists need to find the position but the insertion is fast. |
| Deletion at Head | O(N) |
O(1) |
Arrays require shifting; linked lists just update the head. |
| Deletion in Middle | O(N) |
O(N) (find) + O(1) (delete) |
Arrays must shift elements. Linked lists need to find the position but the deletion is fast. |
| Memory Usage | Minimal overhead | Higher overhead (pointers) | Linked lists store one or two extra pointers per element. |
| Cache Performance | Excellent | Poor | Arrays have great spatial locality; linked list nodes are scattered in memory. |
Test your understanding!
You are designing a text editor. A core feature is the ability to type characters anywhere in the document. When a user types a character, it should appear instantly. Which data structure, a dynamic array or a doubly linked list, would be a better choice to store the characters of the document? Justify your answer based on the performance of the key operation.
Show answer
A doubly linked list would be a much better choice.
The key operation is insertion in the middle of the document, which happens every time the user types a character (unless they are at the very beginning or end).
- With a dynamic array, inserting a character in the middle would be an
O(N)operation, where N is the number of characters after the cursor. This would require shifting all subsequent characters one position to the right. For a large document, this would be unacceptably slow and would not feel "instant." - With a doubly linked list, the cursor's position can be represented by a pointer to a node. Inserting a new character is an
O(1)operation: create a new node and update thenextandprevpointers of its neighbors. This is extremely fast, regardless of the document's size.
The O(N) access time of a linked list is not a major issue here, as the user typically moves the cursor sequentially (using arrow keys, which is just moving the pointer to next or prev) rather than jumping to an arbitrary index.
Conclusion
In this lesson, you've learned about the linked list, a data structure that excels where arrays falter: efficient insertion and deletion.
Key Takeaways:
- Linked lists are made of nodes connected by pointers, allowing them to be stored non-contiguously in memory.
- Singly linked lists allow
O(1)insertions/deletions at the head but only support forward traversal. - Doubly linked lists add a
prevpointer, enabling bidirectional traversal andO(1)deletion of any node given a direct reference to it, at the cost of higher memory usage. - The primary trade-off vs. arrays is
O(N)access time and poor cache locality in exchange forO(1)insertion/deletion at known positions.
Next Steps
We now have two fundamental sequential data structures in our toolkit: the array and the linked list. Many other data structures are built on top of them. In our next lesson, we will explore two of the most common: the stack and the queue. We will see how both can be implemented using either an array or a linked list, and we'll analyze the trade-offs of each choice.