Minimize Cyclomatic Complexity
Shelfwise's shipping function has grown one if at a time. Now nobody is sure which combination of country, weight and total leads to which price, and the tests cover only half of the cases. There is a number that measures this problem, and a set of habits that shrink it.
The rule
Every branch is another path to test; flatten and name them.
Cyclomatic complexity counts the independent paths through a function. The quick way to work it out: decision points + 1. Decision points are if, elif, for, while, except and case, and many tools also count each and and or. An else adds no decision; it is the other side of the if.
def shipping_cost(order):
if order.country == "IN":
if order.total >= 500:
return 0
else:
return 40
else:
if order.express:
return 900
else:
return 600Three ifs, so complexity is 3 + 1 = 4. That means four paths, and at least four tests to exercise them all.
Three habits bring the number down:
- Guard clauses. Handle the special case first and return early, so the main path is not nested.
- Lookup tables. A dictionary replaces a long
if/elifchain that maps one value to another. - The right construct. Built-ins such as
any(),all(),sum()andmax()say what you mean and hide the loop.
RATES = {"IN": 40, "US": 600, "UK": 550}
def shipping_cost(order):
if order.total >= FREE_SHIPPING_THRESHOLD:
return 0
return RATES.get(order.country, DEFAULT_RATE)This version has one decision, and a new country is a new row of data rather than a new branch. (Notice it also changed the rules: free shipping now applies in every country, and the express price is gone. Flattening is a good moment to check which of the old branches were really intended, and to keep the ones that were.)
The shortcut
Scan for decision keywords, count them, add one. If the number passes about 10, the function is asking to be split or flattened.
List the decisions
for line in cart:is one.if line.qty > line.book.stock:is two.
The trap is thinking a short function is automatically simple. Five ifs squeezed into eight lines still means six paths.
Drills
Answer fast — trust the rule.
A function has one for loop containing one if. What is its cyclomatic complexity?
What does a guard clause do?
A 12-branch if/elif chain maps a country code to a tax rate. What is the best replacement?
Which is the clearest way to ask "is every line in the cart paid for?"
Remember
- Complexity is decision points + 1; each path is another case to test.
- Flatten with guard clauses, lookup tables and built-ins like
any()andall(). - Trap: short does not mean simple; count the branches, not the lines.
