Cyclomatic complexity

Cyclomatic complexity is used to measure the complexity of a program

Oct 26, 2025

A term I learned from Sonar checks. Cyclomatic complexity is used to measure the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program’s source code. It was developed by Thomas J. McCabe, Sr. in 1976.

M = E - N + 2P

where

  • M = Cyclomatic Complexity
  • E = the number of edges of the graph.
  • N = the number of nodes of the graph.
  • P = the number of connected components.

But in practice, a simpler way to compute it (for a single function) is:

M = Number of decision points + 1

What counts as a decision point?

Each of the following contributes +1 to the cyclomatic complexity:

  • if, elif, else if
  • for, while
  • case/switch conditions
  • Logical operators and, or within conditions
  • try/except blocks

Function with 1 Cyclomatic complexity

def add_numbers(a, b):
    # Path 1 starts here
    total = a + b
    print(f"The total is: {total}")
    return total
    # Path 1 ends here

Regardless of the input, the function will always run top to bottom in one path in the exact same order of lines.

Function with 4 Cyclomatic complexity

def get_letter_grade(score):
    # Base path
    
    if score >= 90:        # Decision 1
        # Path 1
        return "A"
    elif score >= 80:      # Decision 2
        # Path 2
        return "B"
    elif score >= 70:      # Decision 3
        # Path 3
        return "C"
    else:
        # Path 4
        return "F"
  • There are four distinct paths a program can take to get a result:
    1. score >= 90 is true.
    2. score >= 90 is false, AND score >= 80 is true.
    3. score >= 90 is false, AND score >= 80 is false, AND score >= 70 is true.
    4. All previous conditions are false.

Function with 15 Cyclomatic complexity

def process_user_order(user, cart, inventory):
    # Base path = 1
    if not user:                          # Decision 1
        raise ValueError("No user provided")
        
    if not cart:                          # Decision 2
        print("Cart is empty")
        return False

    total_price = 0
    
    # Decision 3
    for item in cart:
        # Decision 4
        if item['quantity'] <= 0:
            continue # Skip invalid items

        # Decision 5
        if item['id'] not in inventory:
            print(f"Item {item['id']} not in inventory")
            return False
            
        stock = inventory[item['id']]
        
        # Decision 6                # Decision 7
        if stock['count'] < item['quantity'] and not stock['backorderable']:
            print(f"Not enough stock for {item['id']}")
            return False # Stop processing

        # Decision 8
        if user['is_premium_member']:
            total_price += item['price'] * 0.9
        else:
            total_price += item['price']

        # Decision 9
        if item['category'] == 'Luxury':
            # Decision 10
            if not user['is_verified']:
                print("User must be verified for luxury goods")
                return False
            else:
                total_price += 20 # Luxury tax
    
    # Decision 11
    if total_price > user['credit_limit']:
        print("Over credit limit")
        return False

    # Decision 12
    if user['country'] == 'USA' or user['country'] == 'CAN': # Decision 13
        # Decision 14
        if total_price > 1000:
            print("Requires manager approval")
    
    print("Order processed successfully")
    return True

This function has 15 independent paths (14 + 1). To test it fully, 15 different use cases have to be written. During debugging, the programmer has to consider all 15 paths to effectively debug the code. This makes functions with higher cyclomatic complexity hard to test, too brittle for changes, and hard to maintain in the long run.