Developed in 1955 by Allen Newell, Cliff Shaw, and Herbert A. Simon at RAND Corporation, the linked list is a dynamically-allocated data structure that grows as needed in memory.


Core Components: Nodes

Nodes are individual containers holding a single element, linked via pointers.

  • Head Pointer: Global pointer to the first node; primary entry point for traversal.
  • Tail Pointer: Global pointer to the last node.
  • Access Limitation: Only head and tail have direct access; all other nodes require traversal starting from these points.

Structure Variations

FeatureSingly-Linked ListDoubly-Linked List
Pointers per Node1 (Points to the next node)2 (Points to next and previous)
DirectionUnidirectional (Forward only)Bidirectional (Forward and Backward)
TerminationTail points to NULLHead.prev and Tail.next point to NULL
Illustration

If I only have direct access to the head or tail pointer in a Linked List, and I can only access the other elements by following the nodes' pointers, what is the worst-case time complexity of finding an element in a Linked List with elements?


Operations and Complexity

Searching

Because data is not stored contiguously, random access is impossible. Finding an element requires iterating through nodes one-by-one.

  • Worst-Case Complexity: O(n)
  • Optimization: In a Doubly-Linked List, if the index is closer to the end, you can start at the tail and traverse backward to save time.

# Search by element
def find(element):
    current = head
    while current is not NULL:
        if current.data == element: return True
        current = current.next
    return False
 
# Search by index
def find(index):
    if index < 0 or index >= n: return NULL
    current = head
    for i from 0 to index-1:
        current = current.next
    return current

Notice that when we look for an element in the middle of the Linked List, we start at the head and step forward. This works fine if the index is towards the beginning of the list, but what about when the index of interest is large (closer to )? Can we speed things up?

Inserting

  1. Find Target: Locate the position directly before the insertion index.
  2. Rearrange Pointers:
    • New node’s next → node previously at index i.
    • New node’s prev → node before index i.
    • Node before index i’s next → New node.
    • Node previously at index i’s prev → New node.
Insertion PointComplexityNote
Head / TailO(1)Direct access via global pointers.
MiddleO(n)Requires traversal to find the site.

# inserts newnode at position "index" of Linked List
insert(newnode, index):  
	if index == 0:                  # special case for insertion at beginning of list 
		newnode.next = head 
		head.prev = newnode 
		head = newnode 
	else if index == size:          # special case for insertion at end of list 
		newnode.prev = tail 
		tail.next = newnode 
		tail = newnode 
	else:                           # general case for insertion in middle of list 
		curr = head 
		repeat index-1 times:       # move curr to directly before insertion site 
			curr = curr.next 
		newnode.next = curr.next    # update the pointers 
		newnode.prev = curr 
		curr.next = newnode 
		newnode.next.prev = newnode 
		
	size = size + 1                 # increment size

Removal

  1. Find Target: Locate the node to be removed.
  2. Rearrange Pointers: Link the surrounding nodes to each other, bypassing the target node.

Notice that the node we removed still exists in the diagram. Not considering memory management (only thinking in terms of data structure functionality), is this an issue?

# removes the element at position "index" of Linked List
remove(index):
	if(index == 0):                  # special case for removing from beginning of list
		head = head.next
		head.prev = NULL
	else if index == n - 1:          # special case for removing from end of list
		tail = tail.prev
		tail.next = NULL
	else:                            # general case for removing from middle of list
		curr = head;
		repeat index - 1 times:      # move curr to directly before removal site
			curr = curr.next
		curr.next = curr.next.next   # update the pointers
		curr.next.prev = curr
	n = n - 1                        # decrement n

Summary: Linked Lists vs. Array Lists

FeatureLinked ListArray List
Access/SearchO(n) — Sequential onlyO(1) Access / O(logn) Search (Sorted)
Head Insert/DeleteO(1) — Pointer swapO(n) — Requires shifting all elements
Tail Insert/DeleteO(1) — Pointer swapO(1) Amortized
Memory UsageDynamic — No wasted slotsStatic/Chunked — Potential wasted space
Memory OverheadHigher — Stores data + pointersLower — Stores only data

Key Takeaways:

  • Linked Lists excel at the ends of the list and offer efficient memory growth without pre-allocation.
  • Array Lists dominate in search-heavy scenarios and random access.
  • Neither strategy is strictly superior; the choice depends on the specific frequency of operations in your application.