(Home)

Doubly Linked Lists in TypeScript

[Jul 12, 25]

Introduction

A doubly linked list is a linear data structure consisting of nodes where each node contains three parts: a value element, a reference (or pointer) to the next node, and a reference to the previous node. This bidirectional linking allows traversal in both forward and backward directions, making certain operations like insertion or deletion at both ends or in the middle more efficient compared to a singly linked list.


Implementation

// Define a node for the doubly linked list
class ListNode<T> {
  value: T;
  next: ListNode<T> | null = null;
  prev: ListNode<T> | null = null;

  constructor(value: T) {
    this.value = value;
  }
}

// Doubly Linked List class
class DoublyLinkedList<T> {
  private head: ListNode<T> | null = null;
  private tail: ListNode<T> | null = null;
  private length: number = 0;

  // Check if the list is empty
  isEmpty() {
    return !this.head;
  }

  // Add a value at the end of the list
  append(value: T) {
    const newNode = new ListNode(value);

    if (!this.tail) {
      this.head = this.tail = newNode;
    } else {
      newNode.prev = this.tail;
      this.tail.next = newNode;
      this.tail = newNode;
    }

    this.length++;
  }

  // Add a value at the beginning of the list
  prepend(value: T) {
    const newNode = new ListNode(value);

    if (!this.head) {
      this.head = this.tail = newNode;
    } else {
      newNode.next = this.head;
      this.head.prev = newNode;
      this.head = newNode;
    }

    this.length++;
  }

  // Insert a value at a specific index
  insert(index: number, value: T) {
    if (index < 0 || index > this.length) {
      throw new Error('Index out of bound');
    }

    if (index === 0) {
      this.prepend(value);
    } else if (index === this.length) {
      this.append(value);
    } else {
      const newNode = new ListNode(value);
      const current = this.getNodeAt(index);

      if (current) {
        newNode.prev = current.prev;
        newNode.next = current;

        if (current.prev) current.prev.next = newNode;

        current.prev = newNode;
        this.length++;
      }
    }
  }

  // Remove a node at a specific index
  remove(index: number) {
    if (index < 0 || index >= this.length) {
      return null;
    }

    let removedNode: ListNode<T> | null;

    if (index === 0) {
      removedNode = this.head;
      this.head = this.head?.next || null;

      if (this.head) this.head.prev = null;
      if (this.length === 1) this.tail = null;
    } else if (index === this.length - 1) {
      removedNode = this.tail;
      this.tail = this.tail?.prev || null;

      if (this.tail) this.tail.next = null;
    } else {
      removedNode = this.getNodeAt(index);

      if (removedNode) {
        if (removedNode.prev) removedNode.prev.next = removedNode.next;
        if (removedNode.next) removedNode.next.prev = removedNode.prev;
      }
    }

    this.length--;

    return removedNode ? removedNode.value : null;
  }

  // Get the value at a specific index
  get(index: number) {
    const node = this.getNodeAt(index);

    return node ? node.value : null;
  }

  // Return the index of a given value, or -1 if not found
  indexOf(value: T) {
    let current = this.head;
    let index = 0;

    while (current) {
      if (current.value === value) return index;

      current = current.next;
      index++;
    }

    return -1;
  }

  // Return the size of the list
  size() {
    return this.length;
  }

  // Convert the list to an array
  toArray() {
    const result: Array<T> = [];
    let current = this.head;

    while (current) {
      result.push(current.value);
      current = current.next;
    }

    return result;
  }

  // Convert the list to an array (reversed)
  toReversedArray() {
    const result: Array<T> = [];
    let current = this.tail;

    while (current) {
      result.push(current.value);
      current = current.prev;
    }

    return result;
  }

  // Clear the entire list
  clear() {
    this.head = this.tail = null;
    this.length = 0;
  }

  // Private helper: get node at a given index
  private getNodeAt(index: number): ListNode<T> | null {
    if (index < 0 || index >= this.length) {
      return null;
    }

    let current: ListNode<T> | null;
    let i: number;

    if (index < this.length / 2) {
      current = this.head;
      i = 0;
      while (i < index && current) {
        current = current.next;
        i++;
      }
    } else {
      current = this.tail;
      i = this.length - 1;
      while (i > index && current) {
        current = current.prev;
        i--;
      }
    }

    return current;
  }
}

Example Usage

const list = new DoublyLinkedList<number>();

list.append(10);
list.append(20);
list.prepend(5);
list.insert(1, 15);

console.log(list.toArray()); // => [5, 15, 10, 20]
console.log(list.toReversedArray()); // => [20, 10, 15, 5]
console.log(list.get(2)); // => 10

list.remove(1);

console.log(list.indexOf(20)); // => 2
console.log(list.size()); // => 3