An adjacency list represents a Graph by storing, for each vertex, a list of its neighbors. With vertices, you have lists; the -th list contains every vertex that has an edge to.

Each list is typically a Linked list or a Dynamic array. The container holding the lists is itself an array of length , indexed by vertex.

Properties

  • Size: — proportional to actual data.
  • Per-vertex storage: a list of neighbors plus list overhead.
  • No wasted space for non-edges.

For a sparse graph with millions of vertices but a constant number of edges per vertex, the adjacency list is dramatically smaller than the corresponding adjacency matrix.

Operations

OperationComplexity
Check if edge exists
Add edge to prepend to the list
Remove edge to find and remove
Iterate all neighbors of
Total space

Edge lookup is no longer constant time — to check if there’s an edge from to , you scan ‘s neighbor list. For high-degree vertices, this can be slow.

Implementation

typedef struct edgeNode {
    int dest;
    int weight;             // for weighted graphs
    struct edgeNode *next;
} edgeNode;
 
edgeNode *adj[MAX_V] = {NULL};   // array of list heads
 
void addEdge(int u, int v, int weight, bool directed) {
    edgeNode *node = malloc(sizeof(edgeNode));
    node->dest   = v;
    node->weight = weight;
    node->next   = adj[u];
    adj[u] = node;
    
    if (!directed) {
        node = malloc(sizeof(edgeNode));
        node->dest   = u;
        node->weight = weight;
        node->next   = adj[v];
        adj[v] = node;
    }
}

A new edge is prepended to the source vertex’s list — . For undirected graphs, two list nodes per edge.

When to use

Adjacency lists win when:

  • The graph is sparse. Almost all real-world graphs.
  • Memory is constrained.
  • You frequently iterate neighbors rather than per vertex.
  • You don’t need fast edge-existence checks.

Most graph algorithms in practice use adjacency lists:

  • BFS and DFS: instead of .
  • Dijkstra with priority queue: .
  • Topological sort: .

When not to use

  • Very dense graphs — at , the adjacency list’s overhead (per-node pointers, allocator overhead, scattered memory) makes it slower than a matrix.
  • Need to query edge existence in — common in algorithms that check “is there an edge between any two vertices?” repeatedly.

In these cases, see Adjacency matrix.

Variants

  • Linked list (most common): straightforward, easy to add/remove edges.
  • Dynamic array: better cache behavior, faster iteration.
  • Hash set: edge lookup at the cost of unordered iteration.
  • Sorted array: lookup via binary search if neighbors are sorted.

The choice depends on workload. For typical mixed read/write patterns, linked lists or dynamic arrays are fine.

Effect on algorithm complexity

The same algorithm has different complexities depending on representation:

AlgorithmAdjacency matrixAdjacency list (binary heap PQ)
BFS / DFS
Dijkstra
Prim’s algorithm

The Dijkstra and Prim rows depend on both the graph representation and the priority-queue choice — the bound combines the cost of decrease-key operations (PQ-dependent) with edge enumeration (representation-dependent). Switching from a binary heap to a Fibonacci heap drops Dijkstra and Prim with adjacency lists to . The "" matrix entries are for the simple array-PQ implementation, which on dense graphs is actually faster than the heap version since iterating neighbors costs per vertex either way.

For sparse graphs (), the adjacency-list version is dramatically faster. This is why production graph code almost universally uses lists.

For the matrix alternative, see Adjacency matrix. For graph fundamentals and algorithm context, see Graph.