How to Identify and Reduce Cyclomatic Complexity Using Static Analysis

How to Identify and Reduce Cyclomatic Complexity Using Static Analysis

IN-COMCode Analysis, Impact Analysis Software, IT Risk Management, Legacy Systems, Tech Talk

Keeping code simple and maintainable is a challenge that every developer faces, and cyclomatic complexity plays a big role in that struggle. This metric measures how many different paths exist in a program’s execution, and when it gets too high, software becomes harder to read, debug, and test. Complex code leads to longer development cycles, more bugs, and increased maintenance costs. That’s why reducing complexity isn’t just about writing cleaner code—it’s about improving scalability, reliability, and long-term efficiency.

Static code analysis offers a structured way to tackle complexity by automating the detection of overly intricate logic, excessive branching, and deep nesting. Instead of manually searching for problem areas, developers can rely on these tools to highlight functions that need refactoring. By keeping complexity in check, teams can ensure that their codebase stays readable, scalable, and easier to work with, making software development faster and more efficient.

Table of Contents

Reduce Cyclomatic Complexity

SMART TS XL is Your Ideal Static Code Analyses Solution for that

Understanding Cyclomatic Complexity

What is Cyclomatic Complexity?

Cyclomatic complexity is a software metric that measures the complexity of a program’s control flow. It was introduced by Thomas J. McCabe in 1976 and is used to evaluate the number of independent execution paths within a program. A higher cyclomatic complexity indicates that the code contains more decision points, making it harder to read, maintain, and test.

The metric is calculated based on the control flow graph (CFG) of a program, where:

  • Nodes represent statements or instructions in the code.
  • Edges represent control flow paths between these statements.

The formula for cyclomatic complexity (V) is:

mathematicaCopyEditV(G) = E - N + 2P

Where:

  • E = Number of edges in the control flow graph.
  • N = Number of nodes in the control flow graph.
  • P = Number of connected components (typically 1 for a single program).

A simple program with no loops or conditionals has a cyclomatic complexity of 1, meaning there is only one possible execution path. As conditionals (if-else, loops, switches) increase, so does the complexity.

Why High Cyclomatic Complexity is a Problem?

A high cyclomatic complexity makes software harder to maintain, test, and debug. Some of the key issues include:

  • Increased Maintenance Effort: Complex functions are harder to understand, leading to increased development time when modifying code.
  • Higher Testing Cost: More execution paths require more test cases to achieve full coverage, making unit testing expensive.
  • Greater Bug Probability: Code with a high number of decision points is more likely to contain logic errors and bugs.
  • Reduced Readability: Nested conditions and deeply structured code blocks make understanding the logic difficult, leading to poor code maintainability.

For example, consider a simple Python function that determines if a number is prime:

pythonCopyEditdef is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

This function has a cyclomatic complexity of 3 due to:

  1. The initial if condition (n < 2).
  2. The for loop (for i in range(2, n)).
  3. The if condition inside the loop (if n % i == 0).

A higher cyclomatic complexity would arise if more conditions were added, such as handling specific number patterns or performance optimizations.

How Cyclomatic Complexity is Calculated?

Cyclomatic complexity is calculated by counting the number of linearly independent paths in a program’s control flow graph. Let’s look at examples in different programming environments to understand how it is measured.

Example 1: Java – Calculating Cyclomatic Complexity

javaCopyEditpublic class ComplexityExample {
    public static int findMax(int a, int b, int c) {
        if (a > b && a > c) { 
            return a;
        } else if (b > c) {
            return b;
        } else {
            return c;
        }
    }
}

Control flow analysis:

  • Decision points:
    • The first if condition (a > b && a > c) (1 path split).
    • The else if condition (b > c) (another path split).

Cyclomatic complexity formula:

  • Edges (E) = 5, Nodes (N) = 4, P = 1
  • V(G) = 5 – 4 + 2(1) = 3

Example 2: SQL – Cyclomatic Complexity in Stored Procedures

Cyclomatic complexity is also relevant in SQL stored procedures, especially those containing conditional logic such as IF statements or CASE expressions.

sqlCopyEditCREATE PROCEDURE Check_Order_Status (@order_id INT)
AS
BEGIN
    IF @order_id IS NULL
        PRINT 'Invalid Order ID';
    ELSE
    BEGIN
        IF EXISTS (SELECT 1 FROM Orders WHERE id = @order_id AND status = 'Pending')
            PRINT 'Order is pending';
        ELSE IF EXISTS (SELECT 1 FROM Orders WHERE id = @order_id AND status = 'Completed')
            PRINT 'Order has been completed';
        ELSE
            PRINT 'Order not found';
    END
END;

Control flow breakdown:

  1. First IF condition (@order_id IS NULL).
  2. First EXISTS check (status = 'Pending').
  3. Second EXISTS check (status = 'Completed').
  4. Final ELSE statement.

Applying the formula:

  • Edges (E) = 6, Nodes (N) = 5, P = 1
  • V(G) = 6 – 5 + 2(1) = 3

Example 3: COBOL – Cyclomatic Complexity in Mainframe Applications

Cyclomatic complexity is also an important metric in COBOL programs, where IF-ELSE statements and PERFORM loops increase complexity.

cobolCopyEditIF CUSTOMER-BALANCE > 0 THEN  
    DISPLAY "Customer has a balance due"  
    IF CUSTOMER-BALANCE > 500 THEN  
        DISPLAY "Balance is high"  
    ELSE  
        DISPLAY "Balance is manageable"  
ELSE  
    DISPLAY "No outstanding balance"

Complexity calculation:

  1. First IF CUSTOMER-BALANCE > 0 condition.
  2. Second IF CUSTOMER-BALANCE > 500 condition.
  3. ELSE statement handling balance conditions.

Using the formula:

  • Edges (E) = 5, Nodes (N) = 4, P = 1
  • V(G) = 5 – 4 + 2(1) = 3

Acceptable Cyclomatic Complexity Levels

Industry best practices recommend keeping cyclomatic complexity within a manageable range:

  • 1 – 10: Simple, maintainable code with minimal testing effort.
  • 11 – 20: Moderately complex, requires more testing and refactoring.
  • 21 – 50: High complexity, difficult to test and maintain.
  • 50+: Extremely complex, should be refactored immediately.

The Role of Static Code Analysis in Cyclomatic Complexity Reduction

How Static Code Analysis Identifies Complexity Issues

Static code analysis is a method of evaluating code without executing it, focusing on structural properties, syntax, and logic to detect potential issues. One of its key applications is measuring and reducing cyclomatic complexity, ensuring that code remains readable, maintainable, and testable.

When a static analysis tool scans a codebase, it generates control flow graphs (CFGs) for functions, identifies decision points, and calculates the cyclomatic complexity score. These tools highlight overly complex functions, making it easier for developers to pinpoint problem areas that need refactoring.

For example, in Java, a static analysis tool might detect excessive conditionals and flag the function for complexity reduction:

javaCopyEditpublic int calculateDiscount(int price, boolean isLoyalCustomer, boolean hasCoupon) {
    if (price > 100) {
        if (isLoyalCustomer) {
            if (hasCoupon) {
                return price - 30;
            }
            return price - 20;
        } else if (hasCoupon) {
            return price - 15;
        }
    }
    return price;
}

Static analysis would flag this function as highly complex due to multiple nested conditionals. The tool would suggest breaking it into smaller, modular functions to improve maintainability.

Code Metrics and Complexity Measurement Tools

Static code analysis tools often include complexity measurement features that provide developers with clear insights into their code’s structural complexity. These tools automatically compute cyclomatic complexity scores, helping teams set quality thresholds and detect problematic code early.

Key features in these tools include:

  • Complexity scoring: Automatically assigns a cyclomatic complexity number to each function.
  • Control flow visualization: Generates graphs showing function complexity.
  • Threshold alerts: Flags functions exceeding predefined complexity limits.

For example, in SQL stored procedures, static analysis tools can detect complexity issues caused by excessive nested IF conditions, CASE statements, and loops:

sqlCopyEditCREATE PROCEDURE Calculate_Discount (@customer_id INT, @order_value INT)
AS
BEGIN
    IF @order_value > 500
    BEGIN
        IF @customer_id IN (SELECT vip_id FROM VIP_Customers)
            PRINT 'Apply 20% Discount';
        ELSE
            PRINT 'Apply 10% Discount';
    END
    ELSE IF @order_value > 100
    BEGIN
        PRINT 'Apply 5% Discount';
    END
    ELSE
        PRINT 'No Discount';
END;

A static analysis tool would flag this procedure for excessive decision points, suggesting refactoring to simplify logic.

Automating Complexity Detection with Static Analysis

One of the greatest advantages of static code analysis is its ability to automate complexity detection, ensuring continuous monitoring of code quality without manual intervention.

This is particularly useful in large-scale applications, where hundreds or thousands of functions exist. Instead of manually reviewing each one, static analysis tools automatically scan the entire codebase, detecting complex functions, excessive branching, and deep nesting.

For example, in COBOL, static analysis helps identify complex PERFORM loops and IF-ELSE chains:

cobolCopyEditIF AMOUNT-DUE > 1000 THEN  
    PERFORM LARGE-DISCOUNT-CALCULATION  
ELSE  
    IF AMOUNT-DUE > 500 THEN  
        PERFORM MEDIUM-DISCOUNT-CALCULATION  
    ELSE  
        IF AMOUNT-DUE > 100 THEN  
            PERFORM SMALL-DISCOUNT-CALCULATION  
        ELSE  
            DISPLAY "No Discount".

A static analysis tool would suggest replacing nested IF statements with structured logic, improving readability and reducing complexity.

By integrating static code analysis into CI/CD pipelines, teams can:

  • Automatically detect complex code before deployment.
  • Enforce coding standards by setting cyclomatic complexity limits.
  • Track complexity trends over time, identifying areas that require refactoring.

Techniques for Reducing Cyclomatic Complexity Using Static Code Analysis

Code Refactoring and Function Simplification

One of the most effective ways to reduce cyclomatic complexity is code refactoring, which involves restructuring code without changing its external behavior. Refactoring improves readability, maintainability, and testability while reducing the number of independent execution paths in a program.

Static code analysis tools help identify functions with high complexity scores and suggest refactoring opportunities. A common technique is function simplification, which involves breaking down large, complex functions into smaller, more manageable ones.

Consider the following Python example of a function that calculates discounts:

pythonCopyEditdef calculate_discount(price, customer_type, has_coupon):
    if price > 100:
        if customer_type == "VIP":
            if has_coupon:
                return price * 0.7  # 30% discount
            return price * 0.8  # 20% discount
        elif has_coupon:
            return price * 0.85  # 15% discount
    return price

This function has a cyclomatic complexity of 4 due to its nested conditions. A refactored approach simplifies the logic by extracting calculations into separate functions:

pythonCopyEditdef vip_discount(price, has_coupon):
    return price * 0.7 if has_coupon else price * 0.8

def regular_discount(price):
    return price * 0.85

def calculate_discount(price, customer_type, has_coupon):
    if price > 100:
        return vip_discount(price, has_coupon) if customer_type == "VIP" else regular_discount(price)
    return price

This approach improves code clarity while maintaining the same logic. Static analysis tools detect and recommend such modular function extractions as best practices.

Extracting Complex Logic into Separate Functions

Another common strategy for reducing cyclomatic complexity is decomposing large functions into multiple smaller functions. This not only simplifies control flow but also improves code reuse and unit testability.

For example, consider a Java program that processes orders:

javaCopyEditpublic void processOrder(int orderId, boolean isExpress, boolean isGift) {
    if (orderId > 0) {
        if (isExpress) {
            System.out.println("Processing express order...");
        } else {
            System.out.println("Processing standard order...");
        }
        if (isGift) {
            System.out.println("Adding gift wrap...");
        }
    } else {
        System.out.println("Invalid order ID.");
    }
}

This function has four execution paths, making it harder to maintain. By extracting separate functions for handling express and gift wrap options, complexity is reduced:

javaCopyEditpublic void processOrder(int orderId, boolean isExpress, boolean isGift) {
    if (orderId <= 0) {
        System.out.println("Invalid order ID.");
        return;
    }
    handleOrderType(isExpress);
    handleGiftOption(isGift);
}

private void handleOrderType(boolean isExpress) {
    System.out.println(isExpress ? "Processing express order..." : "Processing standard order...");
}

private void handleGiftOption(boolean isGift) {
    if (isGift) {
        System.out.println("Adding gift wrap...");
    }
}

Now, each function has a single responsibility, making it easier to read and maintain.

Eliminating Unnecessary Conditionals and Loops

Another major contributor to high cyclomatic complexity is excessive conditionals and loops. Many programs include redundant conditions or loops that can be simplified or eliminated using static analysis insights.

For example, in SQL stored procedures, nested IF conditions increase complexity:

sqlCopyEditCREATE PROCEDURE Process_Transaction (@amount INT, @status VARCHAR(10))
AS
BEGIN
    IF @amount > 0
    BEGIN
        IF @status = 'Pending'
            PRINT 'Processing transaction...'
        ELSE IF @status = 'Completed'
            PRINT 'Transaction already completed.'
        ELSE
            PRINT 'Invalid status.'
    END
    ELSE
        PRINT 'Invalid amount.';
END;

A static analysis tool would suggest replacing nested IF conditions with CASE expressions to improve readability and reduce complexity:

sqlCopyEditCREATE PROCEDURE Process_Transaction (@amount INT, @status VARCHAR(10))
AS
BEGIN
    IF @amount <= 0
        PRINT 'Invalid amount.';
    ELSE
        PRINT CASE 
            WHEN @status = 'Pending' THEN 'Processing transaction...'
            WHEN @status = 'Completed' THEN 'Transaction already completed.'
            ELSE 'Invalid status.'
        END;
END;

By restructuring conditionals, code execution paths are reduced, improving efficiency.

Using Design Patterns to Simplify Control Flow

Using design patterns is another technique for reducing cyclomatic complexity. Patterns like strategy, state, and factory help manage decision-heavy logic while maintaining flexibility.

For example, in COBOL, decision-heavy logic can be simplified using structured programming patterns. A program with nested IF conditions for payroll processing:

cobolCopyEditIF EMPLOYEE-TYPE = "FULLTIME" THEN  
    COMPUTE PAY = HOURS-WORKED * FULLTIME-RATE  
ELSE  
    IF EMPLOYEE-TYPE = "PARTTIME" THEN  
        COMPUTE PAY = HOURS-WORKED * PARTTIME-RATE  
    ELSE  
        IF EMPLOYEE-TYPE = "CONTRACT" THEN  
            COMPUTE PAY = HOURS-WORKED * CONTRACT-RATE  
        ELSE  
            DISPLAY "Invalid employee type".

A static analysis tool would recommend using data-driven design, where rates are stored in a lookup table, reducing conditionals:

cobolCopyEditSEARCH EMPLOYEE-RATES  
    WHEN EMPLOYEE-TYPE = RATE-TYPE  
        COMPUTE PAY = HOURS-WORKED * RATE-AMOUNT.

This eliminates deep nesting, making the code more scalable and maintainable.

Best Practices for Managing Code Complexity

Writing Modular and Maintainable Code

One of the most effective ways to manage and reduce cyclomatic complexity is by writing modular and maintainable code. Modular code follows the single responsibility principle, ensuring that each function, method, or procedure handles only one task. This prevents functions from becoming too complex and difficult to maintain.

Static code analysis tools help identify functions that violate modularity by detecting high cyclomatic complexity scores. They also suggest ways to refactor code for better readability and maintainability.

Consider a C++ example where a function processes user authentication, session handling, and logging:

cppCopyEditvoid authenticateUser(std::string username, std::string password) {
    if (username == "admin" && password == "admin123") {
        std::cout << "Login successful" << std::endl;
        // Session creation
        sessionActive = true;
        lastLogin = time(0);
        // Logging event
        logEvent("Admin login detected");
    } else {
        std::cout << "Login failed" << std::endl;
        logEvent("Failed login attempt");
    }
}

This function is handling multiple responsibilities—authentication, session creation, and logging. A static analysis tool would recommend breaking it into three separate functions:

cppCopyEditbool validateCredentials(std::string username, std::string password) {
    return username == "admin" && password == "admin123";
}

void createSession() {
    sessionActive = true;
    lastLogin = time(0);
}

void authenticateUser(std::string username, std::string password) {
    if (validateCredentials(username, password)) {
        std::cout << "Login successful" << std::endl;
        createSession();
        logEvent("Admin login detected");
    } else {
        std::cout << "Login failed" << std::endl;
        logEvent("Failed login attempt");
    }
}

This refactored code is more modular and maintainable, ensuring each function focuses on a single responsibility.

By following modular design principles, developers can:

  • Improve code readability and maintainability.
  • Reduce the risk of logic errors in complex functions.
  • Make testing and debugging more efficient.

Leveraging Static Analysis for Continuous Complexity Monitoring

Managing code complexity is an ongoing process, and static code analysis provides a way to continuously monitor and enforce complexity standards throughout a project’s lifecycle.

By integrating static analysis tools into the development pipeline, teams can:

  • Automatically track complexity scores for each function or method.
  • Set complexity thresholds to prevent overly complex functions.
  • Generate reports to track complexity trends over time.

For example, in SQL stored procedures, complexity can grow due to nested conditions and joins. A static analysis tool can flag high-complexity queries for optimization.

sqlCopyEditCREATE PROCEDURE Get_Customer_Orders (@customer_id INT)
AS
BEGIN
    SELECT o.order_id, o.amount, c.customer_name
    FROM Orders o
    JOIN Customers c ON o.customer_id = c.customer_id
    WHERE c.customer_id = @customer_id
    AND o.amount > 500
    AND o.status = 'Completed';
END;

A tool might recommend breaking complex query conditions into views or separate stored procedures, improving efficiency and maintainability.

By continuously monitoring complexity, teams can enforce coding best practices, reduce technical debt, and maintain high software quality.

Setting Complexity Thresholds in CI/CD Pipelines

To prevent excessive code complexity, organizations can enforce complexity thresholds within Continuous Integration/Continuous Deployment (CI/CD) pipelines. This ensures that new code adheres to complexity standards before merging into the main codebase.

A typical CI/CD pipeline rule for static analysis includes:

  1. Setting a cyclomatic complexity threshold (e.g., functions exceeding 10 complexity points must be refactored).
  2. Blocking pull requests that introduce high-complexity code.
  3. Generating automated reports to track complexity trends.

For example, in JavaScript, a static analysis tool like ESLint can be configured to flag high complexity:

jsonCopyEdit"rules": {
    "complexity": ["error", { "max": 10 }]
}

If a developer writes a complex function, it triggers an alert in the pipeline:

javascriptCopyEditfunction processOrder(order) {
    if (order.status === "Pending") {
        if (order.amount > 100) {
            if (order.customerType === "VIP") {
                return "VIP discount applied";
            } else {
                return "Standard discount applied";
            }
        } else {
            return "No discount";
        }
    } else if (order.status === "Completed") {
        return "Order already processed";
    }
}

The CI/CD pipeline would block this code due to excessive conditionals, requiring the developer to refactor it before merging.

Reducing Code Complexity with SMART TS XL

Managing cyclomatic complexity is essential for writing maintainable, scalable, and testable software, and SMART TS XL provides a comprehensive solution for detecting, analyzing, and optimizing complex code structures. With its advanced static code analysis capabilities, SMART TS XL helps developers identify high-complexity areas, refactor code efficiently, and enforce coding standards to ensure long-term maintainability.

Automated Complexity Detection and Real-Time Analysis

SMART TS XL integrates automated complexity detection, scanning codebases to calculate cyclomatic complexity scores and highlight areas requiring refactoring. It generates detailed reports and visual representations of control flow, enabling developers to quickly pinpoint nested conditionals, excessive loops, and deeply structured logic that increase complexity.

For example, in Java applications, SMART TS XL can detect functions exceeding predefined complexity thresholds:

javaCopyEditpublic void processTransaction(int amount, boolean isPremium, boolean hasDiscount) {
    if (amount > 1000) {
        if (isPremium) {
            if (hasDiscount) {
                applyDiscount(amount, 20);
            } else {
                applyDiscount(amount, 10);
            }
        } else {
            applyDiscount(amount, 5);
        }
    } else {
        logTransaction(amount);
    }
}

SMART TS XL would flag this function for excessive branching and suggest modularizing the logic into separate functions, improving readability and testability.

Code Refactoring Suggestions for Complexity Reduction

SMART TS XL not only detects complexity issues but also provides automated recommendations to refactor code for better maintainability. It suggests:

  • Breaking large functions into smaller, reusable methods.
  • Replacing deeply nested conditionals with switch-case structures or lookup tables.
  • Using design patterns such as strategy and factory patterns to simplify decision-making logic.

In SQL stored procedures, SMART TS XL can analyze query structures and recommend replacing nested IF conditions with CASE expressions for better readability and efficiency:

sqlCopyEditSELECT 
    CASE 
        WHEN amount > 1000 THEN 'High-value transaction'
        WHEN amount > 500 THEN 'Medium-value transaction'
        ELSE 'Low-value transaction'
    END AS transaction_category
FROM Orders;

This simplifies logic while maintaining the same business rules, reducing cyclomatic complexity in database operations.

Seamless Integration into CI/CD Pipelines

To ensure continuous code quality, SMART TS XL integrates seamlessly with CI/CD pipelines, allowing teams to:

  • Automatically scan new code for complexity issues before merging changes.
  • Block commits that exceed complexity thresholds.
  • Provide real-time feedback to developers on code maintainability.

Achieving Code Simplicity with Static Analysis

Managing cyclomatic complexity is essential for writing maintainable, scalable, and efficient software. High complexity increases technical debt, testing costs, and debugging difficulties, making it harder to manage large codebases. Static code analysis plays a critical role in detecting complexity issues early, providing developers with insights into deeply nested logic, excessive branching, and redundant conditionals. By leveraging automated tools, teams can refactor code effectively, simplify control flow, and enforce best practices to improve readability and long-term maintainability.

SMART TS XL enhances complexity management by offering automated complexity detection, code refactoring recommendations, and seamless CI/CD integration. Its real-time feedback and threshold-based enforcement help teams keep code clean and scalable while reducing bugs and security risks. As software development evolves, adopting proactive complexity monitoring ensures better performance, maintainability, and collaboration. By integrating static analysis and automated refactoring tools, developers can write simpler, more effective code that stands the test of time.