code complexity & repetition analysis tool

Cyclomatic Complexity#

What is Cyclomatic Complexity?#

Cyclomatic Complexity (CC), introduced by Thomas McCabe in 1976, measures the number of independent paths through a program's source code. It provides a quantitative measure of code complexity.

How It Works#

Mccabre uses a simplified formula:

CC = (number of decision points) + 1

Decision Points#

A decision point is any control flow statement that creates a branch:

  • if, else if
  • while, for, loop
  • switch, match, case
  • catch
  • Logical operators: &&, ||
  • Ternary operator: ?

Example#

function checkUser(user) {  // CC starts at 1
    if (!user) {             // +1 = 2
        return false;
    }

    if (user.age > 18 && user.verified) {  // +1 (if) +1 (&&) = 4
        return true;
    }

    return false;
}
// Total CC = 4

Interpretation#

CC Range Risk Level Recommendation
1-10 Low Simple, easy to test
11-20 Moderate Consider refactoring
21-50 High Should refactor
50+ Very High Urgent refactoring needed

Why It Matters#

Testing Complexity#

Higher CC means:

  • More test cases needed for full coverage
  • Higher chance of bugs
  • Harder to understand and maintain

A function with CC=10 requires at least 10 test cases to cover all paths.

Maintenance Burden#

Complex functions are:

  • Harder to modify without introducing bugs
  • More difficult for new developers to understand
  • More prone to subtle edge cases

Reducing Complexity#

Extract Methods#

Before (CC=8):

function processOrder(order) {
    if (!order.id) throw new Error("No ID");
    if (order.status === "cancelled") return null;
    if (order.items.length === 0) throw new Error("No items");

    let total = 0;
    for (let item of order.items) {
        if (item.price && item.quantity) {
            total += item.price * item.quantity;
        }
    }

    return total;
}

After (CC=3 + 3 = 6 total):

function processOrder(order) {  // CC=3
    validateOrder(order);
    return calculateTotal(order);
}

function validateOrder(order) {  // CC=3
    if (!order.id) throw new Error("No ID");
    if (order.status === "cancelled") return null;
    if (order.items.length === 0) throw new Error("No items");
}

function calculateTotal(order) {  // CC=2
    let total = 0;
    for (let item of order.items) {
        if (item.price && item.quantity) {
            total += item.price * item.quantity;
        }
    }
    return total;
}

Use Early Returns#

Before:

fn check(x: i32) -> bool {  // CC=3
    let mut result = false;
    if x > 0 {
        if x < 100 {
            result = true;
        }
    }
    result
}

After:

fn check(x: i32) -> bool {  // CC=3 (same but cleaner)
    if x <= 0 { return false; }
    if x >= 100 { return false; }
    true
}

Replace Complex Conditions#

Before:

if ((user.role === "admin" || user.role === "moderator") &&
    user.active && !user.suspended) {  // CC contribution: 4
    // ...
}

After:

function canModerate(user) {  // CC=3
    const isModerator = user.role === "admin" || user.role === "moderator";
    return isModerator && user.active && !user.suspended;
}

if (canModerate(user)) {  // CC=1
    // ...
}

Using with Mccabre#

Check Specific Files#

mccabre complexity src/complex.js

Set Custom Threshold#

mccabre complexity . --threshold 15

JSON Output for CI#

mccabre complexity . --json | jq '.files[] | select(.cyclomatic.file_complexity > 20)'

References#

See Also#