Skip to content
Hello Python
Data Structure1 Practice11 Interview

Linked List

Node sequence connected by references, favoring local insertion over random access. This guide turns that contract into deliberate Python operations, interview invariants, and a runnable practice loop.

Recognize it when

Consider Linked List when the prompt's constraints and required operations match this shape: Node sequence connected by references, favoring local insertion over random access.

Pybit studies a professional Linked List interview workspace with precise technical objects.
On this page · Mental Model

Checking your account…

Sign in to save your code and progress across devices. The lesson and problem statement remain public.

Linked List Code Labs

Mental Model

A linked list stores order in references, not in adjacent array positions. A node owns a value and a next reference; the head is the only external entry point required to reach the chain. There is no constant-time list[index] operation. To reach the kth node, follow k links.

In the Labs, an integer array represents those references: next_indices[node] is the next node or -1. The representation makes pointer changes observable without hiding the central ownership rule inside a helper class.

Nodes, References, and Ownership

Python variables hold references to node objects. Two names may alias the same node, so changing node.next is visible through every alias. Reassigning the local variable node only moves that one cursor; it does not change the chain.

The key question before mutation is: which reference owns the edge being replaced? Removing a node means updating its predecessor’s next, or updating the external head when there is no predecessor.

Traverse Without Random Access

Start at the head and repeatedly follow next. Before each step, the output contains exactly the nodes already visited in chain order, and the cursor names the next unvisited node. This scan is O(n) time and O(1) auxiliary space when it only aggregates; materializing visited values costs O(n) output space.

Follow a Linked Chain

Reference
def chain_order(next_indices, head):
    order = []
    current = head
    while current != -1:
        order.append(current)
        current = next_indices[current]
    return order
Practice

Implement chain_order(next_indices, head). Follow next references from head until -1 and return the visited node indices in chain order.

Public tests

  • Verify reference order wins
  • Verify empty head boundary

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Pointer updates must preserve the successor before overwriting the edge that reaches it. During reversal, keep three meanings stable: previous is the reversed prefix head, current is the next node to move, and successor preserves the untouched suffix. Then redirect current.next, advance both boundaries, and repeat.

Reverse Links Safely

Reference
def reverse_chain(next_indices, head):
    links = next_indices.copy()
    previous = -1
    current = head
    while current != -1:
        successor = links[current]
        links[current] = previous
        previous = current
        current = successor
    return links, previous
Practice

Implement reverse_chain(next_indices, head). Return a copied next-index array and the new head after reversing only the reachable chain.

Public tests

  • Verify successor preservation
  • Verify input links stay unchanged

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

If successor is read after the redirect, the original suffix is no longer reachable from the cursor. This is the linked-list version of overwriting data before saving it.

Use a Sentinel for Head Boundaries

A sentinel is a temporary predecessor whose next points at the real head. It lets deletion use the same “predecessor skips target” transition even when the target is currently first. Return the sentinel’s updated successor as the new head; the sentinel itself is not part of the result.

Bypass a Node through a Sentinel

Reference
def bypass_node(next_indices, head, remove_index):
    sentinel = len(next_indices)
    links = next_indices.copy() + [head]
    predecessor = sentinel
    while links[predecessor] != remove_index:
        predecessor = links[predecessor]
    links[predecessor] = links[remove_index]
    return links[:-1], links[sentinel]
Practice

Implement bypass_node(next_indices, head, remove_index). Use a temporary sentinel predecessor and return copied links plus the possibly changed head.

Public tests

  • Verify middle-node bypass
  • Verify sentinel head update

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Sentinels simplify control flow but do not remove the need for a contract. Decide whether the target is guaranteed to exist and whether the removed node must be detached for memory or API semantics.

Choose a Linked List or Another Structure

Choose a linked list when the input is already node-based and the task depends on local relinking, streaming merge, or constant-space reversal. Choose a Python list when indexed access dominates; its compact built-in representation is usually faster and simpler. Choose a deque for an ordinary queue instead of manually maintaining nodes.

Linked lists do not make arbitrary deletion O(1) unless the operation already owns the predecessor or the node contract provides enough local references. Searching for the deletion point remains O(n).

Common Pitfalls

  • Advancing before saving the successor can lose the rest of the chain.
  • Returning the old head after reversal exposes only the former first node.
  • Comparing node values instead of node identity can break problems with duplicate values.
  • Mutating a shared input when the function promises a new chain violates alias expectations.
  • Forgetting an empty head or one-node chain often produces an attribute error or stale reference.

Explain It in an Interview

Name each cursor by meaning before coding: predecessor, current, successor, or sentinel. State which part of the chain is already final and which edge still connects to untouched nodes. Draw one three-node example, then verify the empty and head-changing cases. Complexity follows from how many times each link is visited; pointer assignment is constant time, but locating a node may require a full traversal.

Relink Nodes Safely(opens in a new tab) isolates one edge replacement. Add Two Numbers(opens in a new tab) adds synchronized traversal, carry state, unequal lengths, and result ownership.

Python Version Note

Python 3.11+

The examples use standard Python containers and syntax available in the supported browser runtime; Linked List itself is taught as an interview abstraction.

Verify in Python docs(opens in a new tab)

Complexity & Invariants

Interview bounds depend on the stated representation, input model, and real Python operations.

OperationAverageWorstInterview note
Core Linked List workflowO(n)O(n)Linked-list edits change relationships between nodes; preserve the successor before redirecting the predecessor link. Every untouched node keeps its original next link, and at most one predecessor changes to the removed node’s former successor.

Space

O(n) for the demonstrated Linked List workflow.

Assumptions

  • State the concrete operation and representation before claiming a bound; tree height and graph density can change it.
  • The bound counts the operations in Relink Nodes Safely and does not hide Python slicing, sorting, or copying.

Invariants Worth Saying Aloud

  1. State the precise Linked List invariant before coding and preserve it after every update, traversal step, or recursive return.
  2. Linked-list edits change relationships between nodes; preserve the successor before redirecting the predecessor link. This remains true after every accepted operation.
  3. Every untouched node keeps its original next link, and at most one predecessor changes to the removed node’s former successor. This remains true after every accepted operation.

When To Use Or Avoid Linked List

Use It When

  • Use Linked List when its representation directly supports the prompt's repeated query or update.
  • Use it when the invariant can be stated before coding and preserved after every operation.

Choose Another Tool When

  • Avoid it when a simpler Python sequence or mapping communicates the same contract with less state.
  • Avoid it when building the structure costs more time or space than the number of required queries can justify.

Common Pitfalls

These are the mistakes most likely to survive a happy-path example and fail a boundary case.

Leaving the core transition unfinished

Placeholder code cannot preserve the Linked List invariant or satisfy the public contract.

Prevent it: Implement the smallest complete transition and run the public tests before adding optimizations.

Avoid

def bypass_linked_node(next_indices, remove_index):
    pass

Use instead

def bypass_linked_node(next_indices, remove_index):
    links = next_indices.copy()
    if remove_index == 0:
        return links
    for index, next_index in enumerate(links):
        if next_index == remove_index:
            links[index] = links[remove_index]
            break
    return links

Breaking the central invariant

Clears the removed node’s outgoing link but never redirects its predecessor, so the chain still reaches the removed node.

Prevent it: Keep this invariant visible while editing: State the precise Linked List invariant before coding and preserve it after every update, traversal step, or recursive return.

Avoid

def bypass_linked_node(next_indices, remove_index):
    links = next_indices.copy()
    links[remove_index] = -1
    return links

Use instead

def bypass_linked_node(next_indices, remove_index):
    links = next_indices.copy()
    if remove_index == 0:
        return links
    for index, next_index in enumerate(links):
        if next_index == remove_index:
            links[index] = links[remove_index]
            break
    return links

Hiding Python work inside the loop

Handle empty state, duplicate values, aliasing, and serialization boundaries explicitly in tests.

Prevent it: Account for every slice, copy, sort, membership test, and container mutation before claiming O(n).

Avoid

def bypass_linked_node(next_indices, remove_index):
    links = next_indices.copy()
    links[remove_index] = -1
    return links

Use instead

def bypass_linked_node(next_indices, remove_index):
    links = next_indices.copy()
    if remove_index == 0:
        return links
    for index, next_index in enumerate(links):
        if next_index == remove_index:
            links[index] = links[remove_index]
            break
    return links

Reviewed References

Official Python documentation supports language behavior. Canonical problem pages provide additional practice context.