Skip to main content

Linked Lists

Lists are fundamental data structures that store collections of elements. There are two main types of lists: Array Lists and Linked Lists.

Array Listsโ€‹

Array Lists (or Dynamic Arrays) provide constant-time access to elements and dynamic resizing capabilities.

Key Operationsโ€‹

  • Access: O(1)
  • Search: O(n)
  • Insertion: O(n)
  • Deletion: O(n)

Linked Listsโ€‹

A linked list is a linear data structure where elements are stored in nodes, with each node containing both data and a reference (or link) to the next node in the sequence.

Singly Linked Listsโ€‹

In a singly linked list, each node points to the next node in the sequence. The first node is called the head, and the last node (which points to null) is called the tail.

Structureโ€‹

class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}

class SinglyLinkedList {
constructor() {
this.length = 0;
this.head = null;
this.tail = null;
}
}

Advantagesโ€‹

  • Dynamic size
  • Efficient insertion and deletion at the beginning
  • Flexible memory allocation
  • Easy to grow and shrink

Disadvantagesโ€‹

  • No random access to elements (must traverse from head)
  • Extra memory for storing node references
  • Not cache-friendly due to non-contiguous memory storage

Common Operationsโ€‹

  1. Push - Add to end
  2. Pop - Remove from end
  3. Shift - Remove from beginning
  4. Unshift - Add to beginning
  5. Get - Retrieve node at index
  6. Set - Change value at index
  7. Insert - Add at specific position
  8. Remove - Delete at specific position
  9. Reverse - Reverse the list

Time Complexitiesโ€‹

OperationTime Complexity
AccessO(n)
SearchO(n)
InsertionO(1)*
DeletionO(1)*

* When inserting/deleting at a known position. If you need to search first, it becomes O(n).

Implementation Exampleโ€‹

Here's a complete implementation of a Singly Linked List with all common operations:

class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}

class SinglyLinkedList {
constructor() {
this.length = 0;
this.head = null;
this.tail = null;
}

// Add to end
push(val) {
let newNode = new Node(val);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
return this;
}

// Remove from end
pop() {
if (!this.head) return undefined;

let current = this.head;
let newTail = current;
while (current.next) {
newTail = current;
current = current.next;
}

this.tail = newTail;
this.tail.next = null;
this.length--;

if (this.length === 0) {
this.head = null;
this.tail = null;
}
return current;
}

// Remove from beginning
shift() {
if (!this.head) return undefined;

let oldHead = this.head;
this.head = oldHead.next;
this.length--;

if (this.length === 0) {
this.tail = null;
}
return oldHead;
}

// Add to beginning
unshift(val) {
let newNode = new Node(val);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head = newNode;
}
this.length++;
return this;
}

// Get node at index
get(index) {
if (index < 0 || index >= this.length) return null;
let counter = 0;
let current = this.head;
while (counter !== index) {
current = current.next;
counter++;
}
return current;
}

// Change value at index
set(index, val) {
let foundNode = this.get(index);
if (foundNode) {
foundNode.val = val;
return true;
}
return false;
}

// Insert at specific position
insert(index, val) {
if (index < 0 || index > this.length) return false;
if (index === this.length) return !!this.push(val);
if (index === 0) return !!this.unshift(val);

let newNode = new Node(val);
let prev = this.get(index - 1);
let temp = prev.next;
prev.next = newNode;
newNode.next = temp;
this.length++;
return true;
}

// Remove at specific position
remove(index) {
if (index < 0 || index >= this.length) return undefined;
if (index === 0) return this.shift();
if (index === this.length - 1) return this.pop();

let previousNode = this.get(index - 1);
let removed = previousNode.next;
previousNode.next = removed.next;
this.length--;
return removed;
}

// Reverse the list
reverse() {
let node = this.head;
this.head = this.tail;
this.tail = node;
let next;
let prev = null;

for (let i = 0; i < this.length; i++) {
next = node.next;
node.next = prev;
prev = node;
node = next;
}
return this;
}
}

Visual Representationโ€‹

A singly linked list can be visualized as a chain of nodes:

Head -> [Data|Next] -> [Data|Next] -> [Data|Next] -> [Data|null] <- Tail

Common Use Casesโ€‹

  1. Implementation of Other Data Structures

    • Stacks and Queues
    • Hash Tables (for handling collisions)
    • Adjacency Lists in Graphs
  2. Memory Management

    • Managing memory blocks
    • File systems
  3. Applications

    • Browser history navigation
    • Undo/Redo operations
    • Music playlists

Practice Problemsโ€‹

To master linked lists, try solving these common problems:

  1. Detect a cycle in a linked list
  2. Find the middle element
  3. Reverse a linked list
  4. Merge two sorted linked lists
  5. Remove duplicates
  6. Check if a linked list is a palindrome

Additional Resourcesโ€‹

Comparison: Array Lists vs. Linked Listsโ€‹

Array Listsโ€‹

Pros:

  • Constant-time access to elements
  • Cache-friendly (contiguous memory)
  • Less memory per element

Cons:

  • Fixed size (needs resizing)
  • Insertion/deletion requires shifting elements

Linked Listsโ€‹

Pros:

  • Dynamic size
  • Easy insertion/deletion
  • No resizing needed

Cons:

  • Linear-time access to elements
  • Extra memory for node references
  • Not cache-friendly