Community detection is one of the most important problems in network science: given a graph, how do we find groups of nodes that are more densely connected to each other than to the rest of the graph? The Louvain algorithm is a widely used, fast, and effective method for detecting communities by optimizing a quality function called modularity. In this long-form post we’ll cover everything from the basic idea to the mathematical formulas, a step-by-step verbal walkthrough, a worked example with diagrams, pros and cons, applications, and helpful FAQs.
What is the Louvain Algorithm
The Louvain algorithm is a greedy, hierarchical method for community detection in large networks. It was introduced by Blondel, Guillaume, Lambiotte, and Lefebvre in 2008. The algorithm optimizes modularity, a measure that quantifies the density of edges inside communities compared to what would be expected in a random graph with the same degree distribution. Louvain’s strength lies in its speed and scalability — it can handle very large graphs efficiently — while often producing high-quality partitions in terms of modularity.
At a high level, Louvain operates in two repeating phases:
- Local movement phase — iteratively move nodes to neighboring communities if it increases modularity.
- Aggregation phase — collapse each community into a single node, creating a new, smaller graph; then repeat.
This two-step process is repeated until modularity no longer improves. Because the graph is coarsened after each pass, Louvain often uncovers a hierarchical community structure.
Introduction to the Louvain Algorithm
Community detection seeks to partition nodes into communities (clusters) where connections are denser within communities than between them. Modularity is one popular objective to evaluate partitions. Louvain directly maximizes modularity by local, greedy decisions.
Why Louvain?
- Efficiency: Near-linear time in practice for sparse graphs.
- Scalability: Works on graphs with millions of nodes/edges.
- Quality: Usually finds high-modularity partitions; often better than simple heuristics.
- Hierarchical output: The aggregation phase yields coarse-grained communities, giving a multiscale view.
Where it is used:
- Social networks (detecting friend groups)
- Biology (protein interaction modules)
- Text and citation networks (topic clusters)
- Infrastructure networks (functional modules)
The algorithm is non-deterministic in the sense that the initial node ordering or tie-breaking can change the final partition, and repeated runs may produce slightly different partitions. However, it is fast enough to allow multiple runs for robustness.
Detailed Louvain Algorithm
To explain the algorithm formally, we need the modularity function and the formula for the change in modularity when moving a node between communities.
Notation
- Let G=(V,E) be an undirected (possibly weighted) graph.
- n=∣V∣ number of nodes.
- m=∑(u,v)∈Ewuv total edge weight (for unweighted graphs wuv=1, then m=∣E∣).
- Aij is the adjacency / weight matrix.
- ki=∑jAij is the sum of weights of edges incident on node iii (degree for unweighted).
- A partition assigns each node iii to a community ci.
- δ(ci,cj)=1 if nodes i and j are in the same community, else 0.
- For a community C:
- Σin(C) is the total weight of edges inside community C (each internal edge counted fully).
- Σtot(C) is the total sum of degrees (incident edge weights) of nodes in C, i.e., the sum of ki for i∈Ci.
Modularity
Modularity Q is defined as:

This compares actual internal edge weight to expected internal edge weight in a null model (the configuration model) where edges are placed at random preserving the degree distribution.
Change in modularity when moving a node
The key greedy step requires computing the modularity gain ΔQ when moving a node i from its current community to another community C. The Louvain paper derives a compact expression. If we insert node i into community C (which may be empty or contain other nodes) the change in modularity is:

Where:
- ki,in is the sum of weights of edges between node i and nodes in community C.
- Σin(C) is total internal edge weight of C (before moving i).
- Σtot(C) is the sum of degrees in C (before moving i).
After simplifying algebraically, this often reduces to:

(This simplified formula is commonly used; it assumes consistent counting of internal weights and suits the usual implementation.)
To compute ΔQ efficiently:
- For each candidate community C (typically communities of neighbors of i), compute ki,in quickly by summing weights from iii to members of C.
- Keep track of Σtot(C) and Σin(C) for communities.
Algorithm structure
Phase 1: Local moving of nodes
- Initialize each node in its own community (so n communities).
- Repeat until no improvement:
- For each node i (in some order), consider removing i from its current community and placing it into the community of each neighbor j.
- Compute ΔQ for each possible target community.
- Move i to the community that yields the maximum positive ΔQ. If no positive gain, keep i where it is.
- Continue iterating over all nodes until a full pass produces no node moves that increase modularity.
Phase 2: Aggregation (coarsening)
- Build a new graph where each community from Phase 1 becomes a single node.
- Weight of edge between new nodes (communities) is the sum of weights of edges between the corresponding communities in the original graph.
- Self-loops represent internal community edge weights.
- Repeat Phase 1 on this new graph (i.e., start again with each node in its own community, where nodes now are communities from previous level).
- Continue the two-phased process until modularity does not increase after a full pass.
The final output is a hierarchical community structure and the partition with highest modularity found during the run.
The Louvain algorithm begins by placing every node in its own singleton community. This starting point is simple and guarantees that any improvement in modularity arises from merging nodes into meaningful clusters rather than from an initial arbitrary partition. With each node isolated, the process enters the local movement phase, where nodes are greedily re-assigned to neighboring communities to maximize the modularity gain. The intuition is local: a node should join the community that yields the largest increase in internal connectivity beyond what the null model anticipates. For each node, the algorithm considers only the communities of its immediate neighbors; this limits the search space and reflects the practical assumption that a node’s best community is near its neighborhood.
To decide whether moving a node from its current community to a target community is beneficial, the algorithm computes the modularity change ΔQ. This value weighs two effects: the additional internal edge weight that the node would bring to the target community, and the change in the expected number of internal edges under the random null model. The calculation requires knowledge of the node’s degree, the sum of edge weights linking it to the target community, and the total degree sum of the target community. If ΔQ is positive for some target, the node is moved to the community that yields the maximum positive gain; if none offers a positive ΔQ, the node stays put. This greedy move is repeated for all nodes and multiple passes are made until a full pass produces no moves that increase modularity. Conceptually, this phase sculpts the raw graph into a partition where nodes are locally well-placed.
Once local movements cease to improve modularity, the algorithm enters the aggregation phase. Each community found in the local phase is contracted into a single super-node. Edges between communities become weighted edges between their corresponding super-nodes, with weights equal to the total weight of inter-community edges in the finer graph. Edges that were internal to a community become self-loops on the new super-node representing that community. This coarsening yields a reduced graph that preserves the modularity landscape at a higher level. Starting anew on this smaller graph, the algorithm again performs local movement: initially each super-node is its own community, and the same greedy reassignment process is applied. Because the graph is smaller, the algorithm typically converges quickly at higher levels, and the repeated coarsening reveals a hierarchical community structure.
The process of local optimization followed by aggregation continues iteratively until no modularity improvement can be achieved after an entire cycle. The hierarchical levels produced during coarsening give meaningful multi-scale insights: the partition at the first level reveals granular communities; higher levels reveal how these communities group into larger modules. Louvain’s greedy nature means it is fast but also subject to local optima: the algorithm finds a locally optimal partition relative to single-node moves and the chosen node ordering. In practice, running Louvain multiple times (with different node orderings or randomized elements) and selecting the best-scoring partition can mitigate suboptimal traps.
Computationally, Louvain is efficient because it computes modularity gains using local community statistics that can be updated quickly when the graph is sparse. The most expensive operations are summing neighbor contributions ki,in and updating community totals Σtot and Σin . Implementations use hash maps or arrays keyed by community IDs to accumulate these values during a node’s evaluation. Another practical detail is tie-breaking: when multiple target communities produce the same ΔQ, deterministic or randomized tie-breaking strategies can be applied; different choices can lead to different community labels but often similar modularity.
Finally, the Louvain algorithm is broadly applicable to both unweighted and weighted graphs, and straightforward to extend to directed graphs via symmetrization or directed modularity definitions. While it optimizes modularity, users should be aware of modularity’s resolution limit: small communities in large networks can be missed due to the definition of the null model. Therefore Louvain is powerful and pragmatic, but practitioners sometimes complement it with post-processing (split/merge heuristics), or use modularity variants to discover communities at different scales.
Pseudocode of Louvain Algorithm
function LOUVAIN(G):
# G: graph with weights (unweighted -> weight=1)
current_graph = G
partition = { each node -> own community }
repeat:
# Phase 1: Local moving
improvement = True
while improvement:
improvement = False
for each node i in current_graph (some order):
best_community = community_of(i)
best_gain = 0
remove i temporarily from its community (update community stats)
# consider communities of neighbors:
for each neighbor j of i:
C = community_of(j)
compute ΔQ for placing i into C
if ΔQ > best_gain:
best_gain = ΔQ
best_community = C
# place i into best_community (or revert if best is original)
if best_community != original_community:
move i to best_community (update stats)
improvement = True
# Phase 2: Aggregation
build new_graph where each community becomes a node
if number_of_nodes(new_graph) == number_of_nodes(current_graph):
break
current_graph = new_graph
partition = map old nodes to new community IDs
until no modularity improvement
return partition
Example — How Louvain works
Below is a small worked example using a toy graph of 8 nodes to illustrate the algorithmic flow. The example is simplified for clarity; real implementations use weighted sums and efficient bookkeeping.

Phase 1: Initialization
- Start with each node in its own community:
- communities: {A}, {B}, {C}, {D}, {E}, {F}, {G}, {H}
Phase 1: Local moves pass 1 (high-level description)
Process nodes in order A, B, C, D, E, F, G, H.
- Evaluate A: neighbors B and C. Check ΔQ placing A into {B} or {C}. Since B and C are each singletons, moving A to join B or C typically increases internal edges — suppose best move is join B (community {A,B}).
- Evaluate B: now B is with A. Neighbors A, C, D. Check moves — maybe best to join with C or keep {A,B}. Suppose B and C merge, resulting in {A,B,C}.
- Evaluate C: with {A,B,C} now, C’s moves might keep it.
- Evaluate D: neighbors B and C (in {A,B,C}) and E. D may move into {A,B,C}, forming {A,B,C,D} because that increases internal edges more than joining E.
- Evaluate E, F, G, H: E joins with F and G forming {E,F,G}, H may join {E,F,G} or stay; suppose final local partition becomes {A,B,C,D} and {E,F,G,H}.
At the end of Phase 1, we have two communities: C1={A,B,C,D}, C2={E,F,G,H}. Modularity has increased compared to singleton partition.
Phase 2: Aggregation
- Collapse C1 into super-node X, C2 into super-node Y.
- Edge between X and Y is weight equal to edges between nodes of C1 and C2; here single edge D—E becomes edge X—Y weight 1.
- Each super-node may have internal self-loop weights equal to internal edges in the community.
New aggregated graph:
X — Y
(With possible self-loop weights representing internal community edges.)
Phase 3: Repeat local moves on aggregated graph
- Start with X and Y singleton communities on the new graph. Moving either node into the other would reduce modularity because internal edges lost exceed the benefit of merging; therefore no beneficial move.
- The algorithm terminates with partition {A,B,C,D} and {E,F,G,H}.
This figure shows how local moves cluster nodes and aggregation preserves the community structure at a higher level.

Advantages and disadvantages of Louvain Algorithm
Advantages
- Speed and Scalability: Louvain is very fast in practice, often near-linear for sparse graphs. It scales to millions of nodes and edges, which makes it useful for real-world large datasets.
- Produces Hierarchical Communities: The iterative coarsening yields multiple levels of communities, giving a multiscale or hierarchical view of structure.
- Simplicity and Implementability: The method is relatively simple to implement; many libraries (NetworkX, igraph, Graph-tool, community-louvain) provide implementations.
- Good Modularity Optimization: It usually finds high-modularity partitions, often outperforming naive heuristics.
- Works for Weighted Graphs: Straightforward extension to weighted networks by treating weights in sums.
- Local Computations: Because decisions are local (neighbors’ communities), implementations can be made memory- and time-efficient.
Disadvantages
- Greedy Nature — Local Optima: The algorithm only considers single-node moves and is greedy. It can get stuck in local optima and might miss the global modularity maximum.
- Non-deterministic / Sensitive to Order: Different node orders or tie-breaks can lead to different partitions. Results can vary across runs.
- Resolution Limit: Modularity has an intrinsic resolution limit — communities smaller than a certain scale may not be detected, especially in very large networks.
- Difficulty with Overlapping Communities: Louvain produces hard partitions (each node in exactly one community); it does not detect overlapping communities where nodes belong to multiple groups.
- Problems with Very Small or Very Large Communities: Tends to favor medium-sized communities given modularity’s definition; tiny but meaningful communities may be absorbed into larger ones.
- Edge Cases and Directed Graphs: While extendable, directed graphs require careful adaptation of modularity. Some subtle effects can appear when weights or self-loops exist.
In practice, many users mitigate limitations by running Louvain multiple times (ensemble), by using modularity variants (resolution parameter) to scan scales, or by combining Louvain with post-processing splits and merges.
Applications of the Louvain Algorithm
The versatility and speed of the Louvain algorithm make it an attractive choice in many domains. Here are common application areas:
- Social Networks: Detecting friend groups, communities of interest, or influencer clusters on platforms like Twitter, Facebook, and Reddit.
- Biology and Bioinformatics: Finding modules in gene co-expression networks, protein–protein interaction clusters, or metabolic pathway communities.
- Information Networks: Clustering papers in citation networks or web pages in hyperlink graphs to identify topical communities.
- E-commerce and Recommendation: Grouping products or users by co-purchase or co-browsing networks to improve recommendation systems.
- Infrastructure Networks: Identifying functional modules in power grids, transportation networks, or communication networks.
- Text and NLP: Building graphs of terms/documents (co-occurrence or similarity) to discover topics or document clusters.
- Fraud Detection: In financial networks, communities can indicate rings of collaborators or fraudulent clusters.
- Image and Vision: In computer vision, pixels or regions can be modeled as graph nodes; Louvain can help segment images by grouping similar regions.
- Urban and Mobility Analysis: Community detection on mobility or travel networks to find neighborhoods, commute patterns, or functional zones.
- Economics and Trade: Detecting clusters in international trade networks or corporate ownership graphs.
Because the Louvain algorithm is implemented in many graph libraries and has a low computational burden, it’s often the default first pass for exploratory network analysis. For domain-specific needs, analysts often use Louvain to get an initial partition and then refine or validate clusters with domain knowledge or other algorithms.
Conclusion
The Louvain algorithm remains one of the most practical and widely used methods for community detection in networks. By greedily optimizing the modularity score through local node moves and repeatedly coarsening the graph, Louvain achieves a compelling balance between computational efficiency and partition quality. Its hierarchical output is useful for multi-scale analysis, and its simplicity makes it a staple in graph analysis toolkits. However, users should be aware of the algorithm’s limitations: sensitivity to initialization and ordering, modularity’s resolution limit, and the possibility of local optima. Best-practice usage often involves multiple runs, exploring resolution parameters (if using modularity extensions), and combining Louvain results with domain-specific validation. Overall, if you are looking for a fast, scalable, and generally effective way to detect communities in large graphs, Louvain is an excellent starting point. Use it to explore structure, guide hypotheses, and produce initial clusters that can be refined with specialized methods when needed.
Frequently Asked Questions (FAQs)
Is Louvain deterministic?
No, not strictly. Louvain’s results can vary across runs due to tie-breaking, node order, and implementation details. Running it multiple times and selecting the partition with the best modularity (or building a consensus) is common practice.
How does Louvain differ from other community detection methods like Girvan–Newman or Infomap?
Louvain optimizes modularity greedily and coarsens the graph iteratively, which makes it fast and scalable. Girvan–Newman is divisive and removes edges based on betweenness centrality — it’s more expensive and less scalable. Infomap uses information-theoretic flow compression (random walks) and often yields different community granularity. Each method optimizes a different objective, so choice depends on the problem and the notion of community that best fits.
Can Louvain find overlapping communities?
Classic Louvain produces non-overlapping (hard) partitions. For overlapping communities you need specialized algorithms (e.g., clique percolation, label propagation variants, or mixed-membership models). There are extensions and adaptations that attempt to extract overlapping structure from hierarchical partitions, but they are not part of vanilla Louvain.
What is the modularity resolution limit and how does it affect Louvain?
The resolution limit means modularity may fail to detect communities smaller than a scale that depends on the total number of edges and interconnections. In very large networks, small but dense communities might be merged into larger ones. Practitioners address this by using a resolution parameter (modified modularity), running Louvain at different resolutions, or using alternative quality functions.
How should I implement Louvain efficiently?
Use efficient data structures: adjacency lists for sparse graphs, maps from community IDs to their total degree and internal edge weights, and arrays to accumulate ki,in during node evaluation. Popular graph libraries (igraph, NetworkX with community extensions, graph-tool) already provide optimized implementations. For very large graphs, prefer C++/graph-tool implementations or distributed variants (e.g., Parallel Louvain methods).