donnemartin/interactive-coding-challenges · error · KeyError
neighbor not found
Error message
neighbor not found
What it means
Raised by Node.remove_neighbor(neighbor) when neighbor.key is not present in self.adj_nodes, meaning no edge from this node to that neighbor exists. Because the method deletes from both adj_weights and adj_nodes, removing a non-existent adjacency would corrupt the dicts, so it fails fast with KeyError.
Source
Thrown at graphs_trees/graph/graph_solution.ipynb:205
"\n",
" def __repr__(self):\n",
" return str(self.key)\n",
"\n",
" def __lt__(self, other):\n",
" return self.key < other.key\n",
"\n",
" def add_neighbor(self, neighbor, weight=0):\n",
" if neighbor is None or weight is None:\n",
" raise TypeError('neighbor or weight cannot be None')\n",
" neighbor.incoming_edges += 1\n",
" self.adj_weights[neighbor.key] = weight\n",
" self.adj_nodes[neighbor.key] = neighbor\n",
"\n",
" def remove_neighbor(self, neighbor):\n",
" if neighbor is None:\n",
" raise TypeError('neighbor cannot be None')\n",
" if neighbor.key not in self.adj_nodes:\n",
" raise KeyError('neighbor not found')\n",
" neighbor.incoming_edges -= 1\n",
" del self.adj_weights[neighbor.key]\n",
" del self.adj_nodes[neighbor.key]\n",
"\n",
"\n",
"class Graph:\n",
"\n",
" def __init__(self):\n",
" self.nodes = {} # Key = key, val = Node\n",
"\n",
" def add_node(self, key):\n",
" if key is None:\n",
" raise TypeError('key cannot be None')\n",
" if key not in self.nodes:\n",
" self.nodes[key] = Node(key)\n",
" return self.nodes[key]\n",
"\n",
" def add_edge(self, source_key, dest_key, weight=0):\n",View on GitHub (pinned to 358f2cc604)
Solutions
- Guard the call: check neighbor.key in node.adj_nodes before removing
- For undirected graphs, remove from both endpoints' adj lists exactly once, in the correct order
- Track edge existence in one place (e.g., a set of (src,dst) tuples) instead of manually managing both directions
Example fix
# before
node.remove_neighbor(other) # KeyError: neighbor not found
# after
if other.key in node.adj_nodes:
node.remove_neighbor(other) Defensive patterns
Strategy: validation
Validate before calling
if neighbor is not None and neighbor.key in node.adj_nodes:
node.remove_neighbor(neighbor) Type guard
def edge_exists(node, neighbor):
return neighbor is not None and neighbor.key in node.adj_nodes Try / catch
try:
node.remove_neighbor(neighbor)
except KeyError:
pass # edge absent; treat as no-op or log Prevention
- Check membership before every remove_neighbor call
- For undirected graphs, remove from both endpoints exactly once
When it happens
Trigger: Calling remove_neighbor for a node never passed to add_neighbor/add_edge; removing an undirected edge by calling remove_neighbor on only one side (or the wrong side); double-removal of the same edge.
Common situations: Graph teardown code assuming symmetric adjacency; replaying edge-removal logs where some edges were already applied; desync between adj_weights and adj_nodes after partial manual mutation.
Related errors
AI-assisted analysis of donnemartin/interactive-coding-challenges@358f2cc604 (2026-08-28).
Data as JSON: /api/errors/53629f3a16e7ae94.
Report an issue: GitHub.