Linked ListLoading saved progress…

Linked List

Implement a singly linked list — a chain of nodes where each node holds a value and a pointer to the next node. Unlike an Array, there is no contiguous block of memory; you walk the chain one .next at a time. Your list tracks both a head (first node) and a tail (last node), plus a size counter that stays accurate across every mutation.

You're building a class with five methods: append, prepend, remove, get, and find. The list is also iterable, so [...list] and for (const v of list) work.

Signature

class LinkedList<T> {
  head: { value: T; next: Node | null } | null;
  tail: { value: T; next: Node | null } | null;
  size: number;

  append(value: T): void;             // add to the end
  prepend(value: T): void;            // add to the front
  remove(value: T): boolean;          // remove first occurrence; return true if removed
  get(index: number): T | undefined;  // value at index, or undefined if out of range
  find(predicate: (v: T) => boolean): Node | null; // first matching node, or null
  toArray(): T[];                     // values, in order
  [Symbol.iterator](): Iterator<T>;   // iteration support
}

Examples

const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.toArray(); // [1, 2, 3]
list.size;      // 3

list.prepend(0);
list.toArray(); // [0, 1, 2, 3]

list.remove(2); // true
list.toArray(); // [0, 1, 3]
list.size;      // 3

list.get(0);    // 0
list.get(99);   // undefined

list.find((v) => v > 0)?.value; // 1
const empty = new LinkedList();
empty.remove(42);   // false — no-op on an empty list, never throws
empty.size;         // 0
empty.toArray();    // []
[...empty];         // []

Notes

  • Track head, tail, and size. Every mutation updates all three correctly. append on an empty list must set both head and tail to the new node.
  • remove(value) removes the first occurrence only. If the value appears twice, the second occurrence stays. Return true when something was removed, false otherwise.
  • Removing the head, the tail, or the only node are the cases where most implementations break — head removal reassigns head, tail removal walks to the penultimate node and clears tail, removing a singleton empties both pointers.
  • get(index) returns undefined for out-of-range indices (negative or >= size). Don't throw.
  • find takes a predicate, not a value. Return the first node (not the value) whose value satisfies the predicate, or null.
  • The list is iterable. Implementing [Symbol.iterator] lets for…of and spread work for free.
  • Don't use Array under the hood. Maintaining head/tail/next pointers is the whole point of the exercise.
Loading editor…