Skip to Content
Course content

148: Implementing a Doubly Linked List

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing code for a junior developer who was building a basic text editor. He had implemented the "Undo" functionality using a singly linked list. It worked great for moving backward through the history, but when he tried to implement "Redo," he hit a wall. To move forward again, he had to start from the very beginning of the history list and traverse forward until he reached the point where the user had stopped undoing. As the document grew, the Redo button started lagging. He didn't need a fancy array or a complex tree; he just needed his nodes to know who their parents were.

That's where the doubly linked list comes in. You've already mastered the singly linked list, where each node is a one-way street. In a doubly linked list, we add a second pointer to the node structure. This allows us to traverse the list in both directions, making operations like deleting a node or moving backward significantly more efficient.

Giving Your Nodes a Memory of Where They've Been

To turn a singly linked list into a doubly linked one, we modify the struct. Instead of just having a next pointer, we add a prev pointer. It looks something like this:

struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
};

I like to think of the prev pointer as a breadcrumb trail. In a singly linked list, if you are at node C, you have no idea that node B exists unless you start over from the head. With a doubly linked list, node C explicitly points back to node B. This sounds simple, but it changes the mental model of how you manage your memory. Every time you link a node forward, you now have a reciprocal obligation to link it backward.

The Delicate Dance of Four Pointers

The real challenge—and where most bugs creep in—is the insertion and deletion logic. When you insert a node into the middle of a singly linked list, you only have to update two pointers. In a doubly linked list, you're often juggling four.

Imagine you're inserting node N between node A and node B. You can't just point A to N and N to B. You also have to ensure N points back to A, and B points back to N. If you miss even one of these, you'll end up with a "broken" list that works fine when traversing forward, but crashes your program the moment you try to go backward. I've spent way too many hours debugging segfaults because I forgot to update a single prev pointer during a deletion.

When deleting a node, the process is actually cleaner than in a singly linked list. In a singly list, you need a reference to the previous node to bridge the gap. In a doubly linked list, the node you're deleting already knows who its predecessor is via node->prev. You can simply tell the previous node to point to the next node, and vice versa, then free the memory. It's an elegant trade-off: you use slightly more memory per node to gain a lot of flexibility in how you move through your data.




📋 Practical Task

Implementing a Bidirectional Music Playlist Manager

Your task is to create a simple music playlist manager using a doubly linked list. Each node should represent a "Song" containing a string for the song title.

Write a program that implements the following:

  • A struct Song that contains a char title[50], a next pointer, and a prev pointer.
  • A function void addSong(struct Song** head, char* title) that appends a new song to the end of the list.
  • A function void printPlaylistForward(struct Song* head) that prints all songs from start to finish.
  • A function void printPlaylistBackward(struct Song* tail) that prints all songs from finish to start.

In your main function, add three songs ("Song A", "Song B", "Song C"), print the list forward, and then use the prev pointers to print the list backward starting from the last node. Ensure you properly free all allocated memory before the program exits.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.