Differences Between Linked Lists & Arrays


03-07-2023

A linked list and an array are both data structures used in programming, but they have some important differences in the way they store and access data.

Arrays

An array is a collection of elements of that are stored in a contiguous block of memory. This means that all the elements are stored one after another, and you can access them using an index. Arrays are good for storing and accessing data quickly and efficiently, especially when you know the size of the array beforehand.

// javascript arrays may hold Number, String, Boolean, even Object data types
let arr = [1, 2, '3', true, null];
console.log(arr[1]); // 2

arr.push(6); 
arr.shift(); // 1
console.log(arr); // [2, '3', true, null, 6];

arr.unshift('Dog');
console.log(arr); // ['Dog', 2, '3', true, null, 6]

// O(n) linear searching method
console.log(arr.indexOf('3')) // 2

Time/Space Complexity


Additionally, if there is not enough free space at the beginning of the array to accommodate the new element, the entire array may need to be relocated to a different spot in memory. This can have a space complexity of O(n), where n is the number of elements in the array, because a new block of memory must be allocated and the existing elements must be copied over to the new location.


Linked List

A linked list is a collection of elements that are linked together using pointers. Each element in the list contains a value and a reference to the location of the next element in the list. Because the elements are not stored in a contiguous block of memory, linked lists can be more flexible than arrays, and they can grow or shrink dynamically. However, accessing an element in a linked list can be slower than in an array because you have to follow the pointers to find the element.

// DOUBLY LINKED LIST
class LinkedList {
  constructor () {
    this.head = this.tail = null
  }
  // add to the end of the list / tail
  append(value) {
    // if list is empty
    if (!this.tail) {
      this.head = this.tail = new Node(value)
    }
    // if linkedlist has >= one node
    else {
      let oldTail = this.tail
      this.tail = new Node(value)
      oldTail.next = this.tail
      this.tail.prev = oldTail
    }
  }
  //add to beginning of list / head
  prepend(value) {
    // if list is empty
    if (!this.head) {
      this.head = this.tail = new Node(value)
    }
    // if linkedlist has >= one node
    else {
      let oldHead = this.head
      this.head = new Node(value)
      oldHead.prev = this.head
      this.head.next = oldHead
    }
  }

  deleteHead() {
    // if list is empty (no head)
    if (!this.head) {
      return null
    }
    // if linkedlist has >= 1 node
    else {
      let removedHead = this.head
      // if list has only 1 node left
      if (this.head === this.tail) {
        this.head = this.tail = null
      }
      //if list has >1 node
      else {
        this.head = this.head.next
        this.head.prev = null
      }
      return removedHead.value
    }
  }

  deleteTail() {
    // if list is empty (no tail)
    if (!this.tail) {
      return null
    }
    // if linkedlist has >= one node
    else {
      let removedTail = this.tail
      // if list has only 1 node left
      if (this.head === this.tail) {
        this.tail = this.head = null
      }
      //if list has >1 node
      else {
        this.tail = this.tail.prev
        this.tail.next = null
      }
      return removedTail.value
    }
  }

  search(value) {
    let currentNode = this.head

    while (currentNode) {
      if (currentNode.value === value) {
        return currentNode
      }
      currentNode = currentNode.next
    }

    return null
  }
}

class Node {
  constructor (value, prev, next) {
    this.value = value
    this.next = next || null
    this.prev = prev || null
  }
}

In this implementation, the Linked List class has an append method for adding a new element to the tail(end of the list), a prepend method for adding a new element to the head(start of the list), deleting the head and tail nodes, and searching for a specific value in the list.

Each element in the list is represented by a Node object, which has a value property to store the value of the element, a next property to point to the next element in the list, and a prev property that points to previous element. The head and tail properties of the Linked List class are used to keep track of the first and last elements in the list, respectively.

Time/Space Complexity


The space complexity of a doubly linked list is O(n), where n is the number of nodes in the list. This is because each node in the list requires a constant amount of space to store the node's value and pointers to both the previous and next nodes. The amount of space required for the list grows linearly with the number of nodes in the list.

Compared to a singly linked list, a doubly linked list requires more space due to the additional pointer to the previous node in each node, but the additional pointer to the previous node makes it possible to perform certain operations in constant time that would require linear time in a singly linked list.


Choosing between a linked list and an array depends on the specific requirements of your program and the characteristics of the data you need to store and manipulate. Here are some general guidelines to consider:

In general, the choice between an array and a linked list involves trade-offs between performance, memory usage, and ease of implementation. It's important to carefully analyze the specific requirements and constraints of your program and to benchmark and test different data structures to determine the optimal choice for your use case.