Small Group Tutorials

Here to help students catch up, keep up, and move ahead. Book a consultation here.

How to Learn the Bron–Kerbosch Algorithm: Maximal Cliques, Candidate Sets, Pivoting and Degeneracy Ordering

Wait, What?

A graph can contain an exponential number of answers, so a good algorithm must avoid wasting work without pretending the output itself is small.

The Bron–Kerbosch algorithm is one of the cleanest ways to enumerate all maximal cliques in an undirected graph. It is a beautiful lesson in recursive search because its state has a precise meaning, its pruning is structural rather than magical, and its professional versions show how theory turns into practical graph software.

Quick Answer

Learn Bron–Kerbosch through clique meaning → maximal versus maximum → the R/P/X state → the base case → recursive branching → pivoting → degeneracy ordering → output-sensitive complexity → production validation. Do not begin with an optimized library implementation. Begin by understanding why every maximal clique is produced once and only once.

1. Start With the Object Being Enumerated

A clique is a set of vertices in which every pair is connected by an edge. A clique is maximal if no additional vertex can be added while keeping it a clique. A maximum clique is a clique of largest possible size in the whole graph. These are different jobs. Bron–Kerbosch enumerates maximal cliques; once they are enumerated, the largest can be selected if the maximum clique is required.

This distinction is the first professional habit to build. A three-vertex clique may be maximal even when a five-vertex clique exists elsewhere. “Maximal” means locally unextendable; “maximum” means globally largest.

2. The Three Sets Are the Whole Algorithm

Every recursive call maintains three sets:

  • R: vertices already chosen for the current clique.
  • P: vertices that may still be added to R because they are adjacent to every vertex in R.
  • X: vertices that also connect to every vertex in R but have already been considered earlier on this search frontier.

The invariant is powerful: R is always a clique, and every vertex in P ∪ X can extend R without breaking cliquehood. Once a learner can state that invariant from memory, the recursion stops looking mysterious.

3. Why the Base Case Works

If both P and X are empty, there is no vertex left that can extend R. Therefore R is maximal, and it should be reported. If P is empty but X is not, R is not new: some previously explored vertex could extend it, so reporting R would duplicate or incorrectly classify the result.

This is one of the most useful lessons in recursive algorithm design: a base case is not just “nothing left to do.” It should encode the exact mathematical condition under which an answer is complete.

4. The Basic Recurrence

For each candidate vertex v in P, recurse with v added to R. The new candidate and excluded sets are restricted to neighbours of v because any future clique containing R ∪ {v} must remain fully connected.

BronKerbosch(R, P, X):
    if P is empty and X is empty:
        report R
        return

    for each vertex v in a snapshot of P:
        BronKerbosch(
            R ∪ {v},
            P ∩ N(v),
            X ∩ N(v)
        )
        P := P \ {v}
        X := X ∪ {v}

Moving v from P to X after its branch is explored prevents later branches at the same level from rediscovering cliques that include v.

5. Work a Tiny Graph by Hand

Suppose vertices A, B and C form a triangle, and C is also connected to D. Starting from R = ∅, P = {A,B,C,D}, X = ∅, one branch may build R = {A}, then {A,B}, then {A,B,C}. At that point no remaining vertex can extend the triangle, so {A,B,C} is reported. A different branch can discover {C,D}. The two answers differ in size, yet both are maximal.

Hand traces are worth doing because they expose the role of X. If X is removed carelessly, duplicate maximal cliques appear.

6. Pivoting Removes Branches That Cannot Be Necessary

The classic improvement chooses a pivot u from P ∪ X and branches only on vertices in P that are not neighbours of u. Why can this work? Any maximal clique extending R must either contain u or contain some candidate not adjacent to u; otherwise u itself could be added, contradicting maximality.

A common practical rule is to choose a pivot with many neighbours inside P, because that makes P \ N(u) small. Tomita, Tanaka and Takahashi developed influential pivoting refinements and established optimal worst-case output-generation bounds as a function of the number of vertices.

7. Degeneracy Ordering Helps on Sparse Graphs

Many real networks are sparse even when they are large. A graph’s degeneracy d is the smallest value such that every subgraph contains a vertex of degree at most d. A degeneracy ordering can be found by repeatedly removing a minimum-degree vertex.

Eppstein, Löffler and Strash showed how combining degeneracy ordering with Bron–Kerbosch-style pivoting gives strong bounds for sparse graphs. The practical idea is simple: choose an outer ordering that keeps later candidate sets small, then use pivoting inside the recursive search.

8. Complexity Must Include the Number of Answers

There can be exponentially many maximal cliques. No enumeration algorithm can avoid spending at least enough time to emit them. This changes how complexity should be discussed: measure not only input size, but also output size, graph density, degeneracy and memory behaviour.

The Moon–Moser bound shows that an n-vertex graph can contain as many as 3n/3 maximal cliques. Modern Bron–Kerbosch variants are designed to approach this unavoidable output barrier rather than promise polynomial time for an inherently exponential enumeration task.

9. Represent Sets So Intersections Are Cheap

The mathematics uses set intersections constantly. In code, representation matters. For small or medium graphs, hash sets are clear. For dense graphs with a manageable vertex universe, bitsets can make intersection and difference operations extremely fast through word-level operations. In high-performance implementations, the cost of set algebra often matters more than the apparent depth of the recursion.

10. Separate Enumeration From Decision Problems

Bron–Kerbosch is excellent when the task is “list all maximal cliques” or “stream maximal cliques and analyse them.” If the task is only “is there a clique of size at least k?” or “find one maximum-weight clique,” specialized branch-and-bound, integer programming or problem-specific methods may be more appropriate. Professional algorithm choice begins by matching the output contract to the actual question.

11. Production Uses and Cautions

  • Social and biological networks: maximal cliques can identify tightly connected groups, but real networks may have so many cliques that filtering is essential.
  • Graph analytics libraries: current NetworkX documentation states that find_cliques is based on Bron–Kerbosch with later adaptations and yields cliques lazily rather than storing every answer at once.
  • Memory: stream results when possible. Converting a huge clique iterator immediately into a list can exhaust memory even when the search itself is well implemented.
  • Semantics: self-loops, multiedges and directed edges need an explicit interpretation before applying an undirected simple-graph clique routine.

Common Failure States

  • Confusing maximal clique with maximum clique.
  • Reporting R whenever P is empty and forgetting to check X.
  • Iterating directly over P while mutating it in a language where that invalidates iteration.
  • Forgetting to intersect both P and X with N(v) in recursive calls.
  • Adding pivoting without understanding why only P \ N(u) must be branched on.
  • Judging performance only by n and ignoring graph density, degeneracy and output size.
  • Materialising every result in memory when a generator or callback would suffice.

Practice Ladder

  • Beginner: identify all cliques, maximal cliques and the maximum clique in a five-vertex graph by hand.
  • Foundation: implement the basic R/P/X recursion and verify that each result is maximal.
  • Intermediate: add pivoting and count recursive calls before and after.
  • Advanced: compute a degeneracy ordering and integrate it with pivoted search.
  • Professional: compare hash-set and bitset implementations, stream outputs, and benchmark on sparse versus dense graph families.
  • Verification: cross-check small random graphs against a trusted library implementation such as NetworkX find_cliques.

Learning Hall Boundary

This article owns maximal-clique enumeration with Bron–Kerbosch, pivoting and degeneracy ordering. It does not replace the existing graph-algorithm foundations, maximal-independent-set material, general matching articles or generic recursion instruction. Those remain separate canonical teaching jobs.

Evidence Boundary

The algorithm originates with Coen Bron and Joep Kerbosch, “Algorithm 457: finding all cliques of an undirected graph,” Communications of the ACM 16(9), 1973, DOI 10.1145/362342.362367. Current NetworkX 3.6.1 documentation describes find_cliques as based on Bron–Kerbosch with later adaptations. Important refinements include Tomita, Tanaka and Takahashi (2006) on worst-case enumeration and Eppstein, Löffler and Strash (2010) on sparse-graph enumeration through degeneracy.

Professional rule: you understand Bron–Kerbosch when you can explain what R, P and X mean at every recursive call, prove the base case, and justify each pruning step before measuring its speed.