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.
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
}
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]; // []
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.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.[Symbol.iterator] lets for…of and spread work for free.Array under the hood. Maintaining head/tail/next pointers is the whole point of the exercise.