Module 9 · Algorithms & Data Structures in JavaScript ⏱ 18 min

Arrays vs. Linked Lists

By the end of this lesson you will be able to:
  • Compare sequential data structures and understand when each shines
  • Implement a simple linked list using JavaScript objects
  • Recognise the hidden cost of front insertion in arrays

Arrays are the first structure most programmers meet. They keep items in a neat row, let you grab any item by its position in constant time, and they are built into every language as a default. But that convenience hides a real cost: adding or removing items from the front of an array is expensive because every other item must slide to a new address. When your problem involves lots of insertions at the beginning or middle, the array becomes a silent bottleneck that only shows up in production.

A linked list offers a different deal. Instead of storing items in a single contiguous block of memory, it scatters them across the heap and connects each one to the next with a pointer. You lose instant random access — to find the tenth item you must walk through the first nine — but insertions and deletions become cheap because you only rewrite a pointer or two. Choosing between them is not about which is 'better'; it is about which cost your program actually pays most often.

graph LR
  A["0: Apple"] --> B["1: Banana"]
  B --> C["2: Cherry"]
  C --> D["3: Date"]
  style A fill:#3776ab,color:#fff
  style B fill:#3776ab,color:#fff
  style C fill:#3776ab,color:#fff
  style D fill:#3776ab,color:#fff
Array memory is contiguous: the CPU can jump straight to any index.

How arrays work

In JavaScript, an array is an ordered collection backed by a contiguous chunk of memory. That contiguity is what makes arr[5] instant: the engine multiplies the index by the item size and jumps straight to the address. Appending with push is usually fast, but only because the engine sometimes reserves spare room at the end. When that room runs out, the entire array must be copied to a larger block, an occasional O(n) cost that average-case analysis usually smooths over.

The real pain arrives at the front. unshift must shift every existing element one slot to the right, which is O(n). If you call unshift inside a loop, you have accidentally built an O(n²) algorithm. The same logic applies to splice in the middle: every element after the insertion point must move. Arrays excel at random access and append-only workloads, but they protest loudly when you disturb their order from the front.

Arrays are fast at the end but slow at the front. Press Run.
const arr = [1, 2, 3];
arr.push(4);
console.log(arr);
arr.unshift(0);
console.log(arr);

Linked lists: nodes and pointers

A linked list is a chain of nodes. Each node holds two things: a value and a reference — called next — to the following node. The list itself usually keeps a reference to the head node, which is your entry point. If the list also tracks the tail, appending becomes O(1); otherwise you must walk the entire chain to reach the end.

The key operation is rewiring pointers. To insert a new node at the front, you create the node, point its next at the current head, and then update the list's head to the new node. No existing node moves in memory; only one pointer changes. Removing the front is equally cheap: move the head reference to head.next. The old first node is simply abandoned, and the garbage collector eventually reclaims it.

flowchart LR
  N1["Node A<br/>value: 1<br/>next"] --> N2["Node B<br/>value: 2<br/>next"]
  N2 --> N3["Node C<br/>value: 3<br/>next: null"]
  style N1 fill:#e0900b,color:#fff
  style N2 fill:#e0900b,color:#fff
  style N3 fill:#e0900b,color:#fff
A linked list: each node carries a value and a pointer to the next node.
Build a tiny linked list and walk it with a while loop.
function createNode(value, next = null) {
  return { value, next };
}
const head = createNode(1, createNode(2, createNode(3)));
let current = head;
while (current) {
  console.log(current.value);
  current = current.next;
}

Measuring the difference

The gap between O(1) and O(n) is invisible on ten items but painful on ten thousand. If you are building a queue that adds and removes from opposite ends, an array will spend most of its time shifting elements, while a linked list handles each operation in constant time. The right choice is not about elegance; it is about matching the structure to the workload.

When to choose which

Arrays win when you need fast lookup by position, when you mostly append at the end, or when memory locality matters for cache performance. Because the items sit next to each other in memory, the CPU can prefetch the next item before you even ask for it, making sequential array scans surprisingly fast.

Linked lists win when you are constantly inserting or deleting at the front, when the collection grows and shrinks unpredictably, or when you cannot afford the shifting cost of a large array. They also make it easy to split or join two lists by rewiring a single pointer, something an array cannot do without copying.

In practice, JavaScript's built-in array is so well optimised that a handmade linked list is rarely faster for small data. But the conceptual difference is vital: understanding why an array is fast at index access and slow at front insertion teaches you to read the hidden costs of any data structure, which is the real skill.

flowchart TD
  Q{"Need fast lookup by index?"} -->|Yes| A["Use array"]
  Q -->|No| B{"Lots of front insertions?"}
  B -->|Yes| C["Use linked list"]
  B -->|No| D["Array is usually fine"]
  style A fill:#1e7f3d,color:#fff
  style C fill:#e0900b,color:#fff
Decision tree: arrays for index access, linked lists for front insertion.
Prepending to a linked list is O(1) because only the head pointer changes.
function prepend(head, value) {
  return { value, next: head };
}
let list = null;
list = prepend(list, 3);
list = prepend(list, 2);
list = prepend(list, 1);
console.log(list.value, list.next.value, list.next.next.value);
Exercise

What does this print? Watch the index access and the unshift.

const arr = ["a", "b", "c"];
console.log(arr[1]);
arr.unshift("z");
console.log(arr[0]);
Exercise

Write toArray(head) that takes the head of a linked list and returns an array of its values in order. If head is null, return an empty array.

function toArray(head) {
  // walk the list and collect values
}
Exercise

This function is meant to count the nodes in a linked list, but it gets stuck in an infinite loop. Fix it so it returns the correct count.

function countNodes(head) {
  let count = 0;
  let current = head;
  while (current) {
    count++;
  }
  return count;
}
Exercise

You need a data structure that supports adding items to the front thousands of times per second and rarely looks up by index. Which is the better choice?

Exercise

Write lastNode(head) that returns the last node in a linked list (the one whose next is null). If the list is empty, return null.

function lastNode(head) {
  // walk to the end and return the last node
}

Recap

  • Arrays store items contiguously for O(1) index access, but front insertion is O(n).
  • Linked lists store scattered nodes connected by next pointers; front insertion is O(1).
  • Rewiring pointers is cheaper than shifting memory, but you lose random access.
  • JavaScript arrays are optimised objects; keep them dense and avoid treating them like hash maps.
  • Choose the structure whose cost profile matches the work your program actually does.

Next you will build two classic abstract data structures on top of these foundations: the stack and the queue.

Checkpoint quiz

What is the time complexity of reading an array element by its index?

What is the time complexity of inserting at the front of a linked list?

Go deeper — technical resources