Understand Cognitive Complexity: A Key to Cleaner, More Maintainable Code

Ritik Chourasiya
4 min readJun 30, 2024

--

Understand Cognitive Complexity

As software developers, we strive to write code that is not only functional but also easy to understand and maintain. One crucial metric that helps us achieve this is Cognitive Complexity. Unlike traditional complexity metrics such as Cyclomatic Complexity, which focus on the number of paths through the code, Cognitive Complexity measures how difficult the code is for a human to understand.

What is Cognitive Complexity?

Cognitive Complexity assesses the mental effort required to understand a piece of code. It takes into account several factors:

  • Control Structures: Each control structure (e.g., if statements, loops) increases cognitive complexity. Nested control structures add more to the score than sequential ones.
  • Nesting: Deeper nesting of control structures increases the complexity score significantly because it requires a developer to hold more context in mind.
  • Recursion and Indirection: Recursion, complex expressions, and other forms of indirection (e.g., callbacks, promise chains) increase cognitive complexity.
  • Boolean Expressions: Complex boolean expressions and ternary operators contribute to cognitive complexity due to the mental effort required to parse and understand them.
  • Logical Structure: Code that jumps around a lot (e.g., multiple return statements, breaking out of loops) is harder to follow and thus has a higher cognitive complexity.

Why Does Cognitive Complexity Matter?

The goal of Cognitive Complexity is to reflect how the code’s structure impacts a developer’s ability to understand and maintain it. By keeping cognitive complexity low, you ensure that your codebase remains clean, understandable, and maintainable.

Example of Cognitive Complexity Calculation

Consider the following code snippet:

const processOrder = (order, user, discountCode) => {
let total = 0;
let discount = 0;
let tax = 0.08;
let discountApplied = false;

if (order.items && order.items.length > 0) { // +1
for (let i = 0; i < order.items.length; i++) { // +1
let item = order.items[i];

if (item.quantity > 0) { // +1
let itemTotal = item.price * item.quantity;

if (item.onSale) { // +1
itemTotal = itemTotal * 0.9; // 10% discount for sale items
}

total += itemTotal;
}
}

if (discountCode && !discountApplied) { // +1
if (discountCode === "SAVE10") { // +1
discount = total * 0.1;
} else if (discountCode === "SAVE20") { // +1
discount = total * 0.2;
} else if (discountCode === "SAVE30" && user.isPremium) { // +2
discount = total * 0.3;
} else { // +1
console.log("Invalid discount code or conditions not met.");
}
discountApplied = true;
}

total -= discount;
total += total * tax;
console.log("Total after discounts and tax:", total);
} else { // +1
console.log("Order has no items.");
}
};

Cognitive Complexity Calculation

  1. Top-level if statement (if (order.items && order.items.length > 0)) adds +1.
  2. for loop adds +1.
  3. Nested if statement inside the loop (if (item.quantity > 0)) adds +1.
  4. Another nested if statement inside the previous if (if (item.onSale)) adds +1.
  5. Top-level if statement after the loop (if (discountCode && !discountApplied)) adds +1.
  6. Nested if statement inside the discount code check (if (discountCode === “SAVE10”)) adds +1.
  7. else if statement inside the discount code check (else if (discountCode === “SAVE20”)) adds +1.
  8. Another else if statement with additional condition (else if (discountCode === “SAVE30” && user.isPremium)) adds +2 (1 for the condition, and an additional 1 for the logical AND).
  9. Final else statement inside the discount code check (else) adds +1.
  10. Final else statement at the top level (else) adds +1.

Total Cognitive Complexity

Summing up all the individual contributions, the total cognitive complexity is:

1 (top-level if) + 1 (for loop) + 1 (nested if) + 1 (nested if) + 1 (top-level if after loop) + 1 (nested if) + 1 (else if) + 2 (else if with additional condition) + 1 (final else in discount code check) + 1 (final top-level else) = 11

The total Cognitive Complexity here is 11.

Refactored Function with Reduced Cognitive Complexity

To reduce cognitive complexity, we can break this function into smaller helper functions, each handling a single responsibility.

Step 1: Extract Helper Functions

const calculateItemTotal = (item) => {
let itemTotal = item.price * item.quantity;
if (item.onSale) {
itemTotal *= 0.9; // 10% discount for sale items
}
return itemTotal;
};

const applyDiscount = (total, discountCode, user) => {
let discount = 0;
if (discountCode === "SAVE10") {
discount = total * 0.1;
} else if (discountCode === "SAVE20") {
discount = total * 0.2;
} else if (discountCode === "SAVE30" && user.isPremium) {
discount = total * 0.3;
} else {
console.log("Invalid discount code or conditions not met.");
}
return discount;
};

const calculateTotalWithTax = (total, taxRate) => {
return total + (total * taxRate);
};

Step 2: Refactor the Main Function

const processOrder = (order, user, discountCode) => {
const taxRate = 0.08;
let total = 0;

if (order.items && order.items.length > 0) {
for (let i = 0; i < order.items.length; i++) {
const item = order.items[i];
if (item.quantity > 0) {
total += calculateItemTotal(item);
}
}

const discount = applyDiscount(total, discountCode, user);
total -= discount;
total = calculateTotalWithTax(total, taxRate);

console.log("Total after discounts and tax:", total);
} else {
console.log("Order has no items.");
}
};

Tips to reducing Cognitive Complexity

Refactoring code to reduce cognitive complexity might involve:

  • Breaking Down Large Functions: Decompose large functions into smaller, single-responsibility functions.
  • Flattening Control Structures: Simplify deeply nested control structures.
  • Simplifying Boolean Expressions: Break down complex boolean expressions into simpler statements.
  • Reducing Control Flow Changes: Minimize the number of control flow changes, such as early returns or breaks.

Benefits of Low Cognitive Complexity

  • Maintainability: Easier to understand and modify.
  • Readability: More approachable for new developers.
  • Lower Bug Risk: Less complex code is less prone to errors.

By focusing on reducing cognitive complexity, you can create a codebase that is not only efficient but also a joy to work with. Let’s strive for cleaner, more maintainable code!

--

--

Ritik Chourasiya
Ritik Chourasiya

Written by Ritik Chourasiya

I’m a 22 year old, still undergraduate backend developer based in India, with 2 years of experience in the software development industry.

No responses yet