codingstuff.io
ExploreTutorialsProblemsCS Subjects
Get Started
ExploreTutorialsProblemsCS Subjects
Get Started
codingstuff.io

Master the art of building software through interactive tutorials, real-world problems, and guided projects.

Pune, Maharashtra, India

codingstuffmail@gmail.com

Product

  • Explore
  • Tutorials
  • Problems
  • CS Subjects

Company

  • About
  • Contact
  • Privacy Policy
  • Terms & Conditions
  • Sitemap

© 2026 codingstuff.io. All rights reserved.

Built with ❤️ for developers everywhere

/
/
All Tutorials
🧮

Data Structures & Algorithms

5 / 65 topics
3Arrays4Linked Lists5Doubly Linked Lists6Circular Linked Lists
Tutorials/Data Structures & Algorithms/Doubly Linked Lists
🧮Data Structures & Algorithms

Doubly Linked Lists

Updated 2026-04-20
3 min read

Introduction

A Doubly Linked List is a type of linked list where each node contains two pointers, one pointing to the next node and another pointing to the previous node. This structure allows traversal in both directions, making it more flexible than a singly linked list. In this tutorial, we will explore the core concepts, implementation, operations, and real-world applications of doubly linked lists.

Core Concepts

Node Structure

A node in a doubly linked list consists of three components:

  1. Data: The value stored in the node.
  2. Next Pointer: A reference to the next node in the sequence.
  3. Previous Pointer: A reference to the previous node in the sequence.
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
    this.prev = null;
  }
}

List Structure

A doubly linked list is defined by its head and tail nodes. The head points to the first node, and the tail points to the last node.

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
  }
}

Operations on Doubly Linked Lists

Insertion

Insert at Beginning

To insert a new node at the beginning of the list, update the pointers accordingly:

insertAtBeginning(data) {
  const newNode = new Node(data);
  if (!this.head) {
    this.head = this.tail = newNode;
  } else {
    newNode.next = this.head;
    this.head.prev = newNode;
    this.head = newNode;
  }
}

Insert at End

To insert a new node at the end, update the tail's pointers:

insertAtEnd(data) {
  const newNode = new Node(data);
  if (!this.tail) {
    this.head = this.tail = newNode;
  } else {
    newNode.prev = this.tail;
    this.tail.next = newNode;
    this.tail = newNode;
  }
}

Insert at a Specific Position

To insert at a specific position, traverse the list to find the correct node and update pointers:

insertAtPosition(data, position) {
  if (position < 0) return false;
  
  const newNode = new Node(data);
  let current = this.head;
  let index = 0;

  while (current && index < position - 1) {
    current = current.next;
    index++;
  }

  if (!current) return false; // Position out of bounds

  newNode.next = current.next;
  newNode.prev = current;
  
  if (current.next) {
    current.next.prev = newNode;
  } else {
    this.tail = newNode; // Inserting at the end
  }
  
  current.next = newNode;
}

Deletion

Delete from Beginning

To delete a node from the beginning, update the head pointer:

deleteFromBeginning() {
  if (!this.head) return null;

  const deletedNode = this.head;
  if (this.head === this.tail) {
    this.head = this.tail = null;
  } else {
    this.head = this.head.next;
    this.head.prev = null;
  }

  return deletedNode.data;
}

Delete from End

To delete a node from the end, update the tail pointer:

deleteFromEnd() {
  if (!this.tail) return null;

  const deletedNode = this.tail;
  if (this.head === this.tail) {
    this.head = this.tail = null;
  } else {
    this.tail = this.tail.prev;
    this.tail.next = null;
  }

  return deletedNode.data;
}

Delete a Specific Node

To delete a specific node, update the pointers of its neighbors:

deleteNode(node) {
  if (!node || !this.head) return false;

  if (node === this.head) {
    this.deleteFromBeginning();
  } else if (node === this.tail) {
    this.deleteFromEnd();
  } else {
    node.prev.next = node.next;
    node.next.prev = node.prev;
  }

  return true;
}

Traversal

Forward Traversal

To traverse the list from head to tail:

forwardTraversal() {
  let current = this.head;
  while (current) {
    console.log(current.data);
    current = current.next;
  }
}

Backward Traversal

To traverse the list from tail to head:

backwardTraversal() {
  let current = this.tail;
  while (current) {
    console.log(current.data);
    current = current.prev;
  }
}

Best Practices

  1. Memory Efficiency: Doubly linked lists use more memory than singly linked lists due to the additional previous pointer.
  2. Double-Check Pointers: Always ensure that both next and prev pointers are correctly updated during insertions and deletions to avoid memory leaks or dangling pointers.
  3. Edge Cases: Handle edge cases such as inserting/deleting from an empty list, deleting the only node in the list, etc.

Real-World Applications

  1. Browser Navigation: Browsers use doubly linked lists to manage the history of visited pages, allowing users to navigate forward and backward.
  2. Undo/Redo Operations: Text editors and image editing software often use doubly linked lists to implement undo and redo functionalities.
  3. Caching Mechanisms: Some caching algorithms like LRU (Least Recently Used) can be efficiently implemented using doubly linked lists.

Conclusion

Doubly linked lists offer a versatile data structure with the ability to traverse in both directions, making them suitable for various applications where bidirectional traversal is necessary. Understanding their implementation and operations is crucial for effective problem-solving in software development.

By following this comprehensive guide, you should now have a solid understanding of doubly linked lists, including their core concepts, operations, best practices, and real-world applications.


PreviousLinked ListsNext Circular Linked Lists

Recommended Gear

Linked ListsCircular Linked Lists