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 iffor,whilecase/switchconditions- Logical operators
and,orwithin conditions try/exceptblocks
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:
score >= 90is true.score >= 90is false, ANDscore >= 80is true.score >= 90is false, ANDscore >= 80is false, ANDscore >= 70is true.- 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.