top of page

Spaghetti & Dead code

  • Writer: Anand Nerurkar
    Anand Nerurkar
  • 1 day ago
  • 6 min read

Spaghetti code is messy, tangled code that’s hard to read, understand, change, or test — like a bowl of pasta where everything’s twisted together. 🍝

What it is

Spaghetti code is code with:

  • Poor structure (no clear modules or separation of concerns)

  • Long, monolithic functions or methods

  • Lots of global/shared state

  • Tight coupling and hidden dependencies

  • Lots of copy-paste / duplication

  • Minimal or no tests and no documentation

Why it's bad

  • Hard to understand and maintain

  • Changes introduce bugs in unexpected places

  • Difficult to add features or refactor safely

  • Slows team velocity and increases technical debt

Common causes

  • Rushed deadlines / lack of design

  • No code reviews or standards

  • Single dev working alone for long time

  • Features tacked onto existing code without refactor

  • Lack of tests or CI

Signs you have spaghetti code

  • One file/class contains hundreds or thousands of lines

  • Methods > 200 lines or > 10 responsibilities

  • Many if/else/switch ladders spread across codebase

  • Hard-to-track global variables or singletons used everywhere

  • Repeated code blocks across modules

How to fix / refactor (practical checklist)

  1. Write tests first — add small unit/integration tests around current behavior so you can refactor safely.

  2. Identify boundaries — find logical responsibilities, group related functionality.

  3. Extract methods — break long functions into smaller named methods.

  4. Apply SRP — each class/method should have one responsibility.

  5. Introduce interfaces — decouple implementation from usage.

  6. Move toward layers — controller/service/repo (or equivalent).

  7. Replace duplication — extract common utilities or services.

  8. Introduce patterns where appropriate (Factory, Strategy, Adapter, etc.) — not overengineer.

  9. Refactor incrementally with small commits and CI runs.

  10. Add documentation & code comments for gotchas.

  11. Enforce code review & linters to prevent regression.

Example (Java - quick illustration)

Bad / spaghetti-style:

public class LoanProcessor {
    public String process(InputData d) {
        // 200+ lines mixing validation, DB, business rules, email, logging...
        if (!d.isValid()) return "error";
        // direct JDBC calls here...
        // complex nested ifs for eligibility, interest calculation, fee updates
        // send email by creating socket / SMTP code inline
        return "approved";
    }
}

Refactored (cleaner):

public class LoanController {
    private final LoanService loanService;
    public Response applyLoan(Request req) {
        return loanService.process(req);
    }
}

public class LoanService {
    private final Validator validator;
    private final LoanRepository repo;
    private final NotificationService notifier;
    public Response process(Request r) {
        validator.validate(r);
        var domain = LoanFactory.createFrom(r);
        var result = eligibilityCalculator.check(domain);
        repo.save(domain);
        notifier.sendResult(domain, result);
        return result.toResponse();
    }
}

Each class does one thing; responsibilities are clear and testable.

Tools & metrics to help

  • Static analysis: SonarQube, Checkstyle, PMD, ESLint (for JS)

  • Complexity metrics: cyclomatic complexity, method length

  • Dependency analyzers: JDepend, IntelliJ dependency tools

  • Refactor-friendly IDEs: IntelliJ, VS Code

  • Automated tests & CI (JUnit, Mockito, etc.)

Team/process fixes

  • Establish code style + architecture guidelines

  • Mandatory PR reviews and pair-programming for tricky areas

  • Keep PRs small and atomic

  • Invest in automated test coverage and CI

  • Schedule regular refactor sprints for tech debt

Quick rule of thumb

If you can’t explain what a function/class does in one sentence, it probably needs refactoring.

Want help? I can:

  • Review a short messy function you paste and show a refactor

  • Give a prioritized refactoring plan for a module or service

  • Generate unit tests for a risky piece of code


Dead code is any piece of code that is written but never used or executed.It "sits" in the codebase, increases complexity, but has no impact on program behavior.

🔎 Types of Dead Code

  1. Unreachable code – Code that can never be executed because of logic/flow.

    public int getValue() { return 5; System.out.println("This will never run"); // unreachable }

  2. Unused code – Methods, classes, variables, or constants that are defined but never referenced.

    class LoanService { private int unusedCounter; // never used }

  3. Redundant logic – Duplicated or legacy logic replaced by newer code but not deleted.

    if (x > 10) { // old logic, never actually executed due to earlier conditions }

  4. Commented-out code – Often considered “dead” if left in source for too long.

    // processLoan(request); // old logic, replaced but not removed

⚠️ Why Dead Code is Bad

  • Increases maintenance cost – Developers waste time reading unused code.

  • Confuses team – Harder to understand which code is “real” logic.

  • Larger binaries – Can bloat compiled artifacts (in some cases).

  • Hides bugs – Sometimes dead code looks “important,” leading to mistakes.

✅ How to Detect and Remove

  • Static analysis tools: SonarQube, PMD, Checkstyle, ESLint (for JS), FindBugs.

  • Compiler warnings: Many compilers warn for unreachable/unused variables or imports.

  • Code coverage tools: Jacoco, Istanbul, Coverage.py → show which lines never execute.

  • Code reviews: Encourage removing instead of commenting out code.

🧹 Best Practices

  • Delete unused methods/classes instead of keeping them “just in case” → source control (Git) keeps history.

  • Regularly run linting + coverage reports in CI.

  • Apply YAGNI principle (“You Aren’t Gonna Need It”) — don’t write code you think you’ll need later.

  • If you must keep it temporarily, mark clearly with @Deprecated (Java) or comments + ticket reference.

👉 Quick rule:Dead code = Code that adds no value but adds burden. If in doubt, delete —

🥊 Dead Code vs. Spaghetti Code

Aspect

Dead Code

Spaghetti Code

Definition

Code that is written but never used/executed.

Code that is messy, tangled, unstructured, and hard to follow.

Examples

- Unused variables, methods, classes


- Unreachable statements


- Commented-out legacy code

- Very long functions


- Lots of nested if/else or switch statements


- Mixing DB, business logic, and UI in one place

Impact

- Wasted space


- Confuses developers (“Do we need this?”)


- Slows down reading/understanding

- Hard to debug & maintain


- Small changes break unrelated areas


- Slows feature delivery

When It Happens

- Old features removed but code left behind


- Developers write “future use” code that never gets used


- Refactoring leftovers

- No proper design upfront


- Quick fixes & patches over time


- Lack of modularization and separation of concerns

Detection

- Static analysis (SonarQube, PMD, ESLint)


- Compiler warnings


- Code coverage reports

- Harder to auto-detect


- Symptoms: high cyclomatic complexity, long classes/functions, low cohesion

Fix

- Delete unused code (Git keeps history)


- Use @Deprecated if you must keep it temporarily

- Refactor into smaller, testable modules


- Apply SOLID principles


- Add proper layers & patterns

Analogy

A dead branch on a tree 🌿 (it’s there but lifeless, does nothing)

A tangled bowl of spaghetti 🍝 (everything mixed, can’t pull one strand cleanly)

Quick Rule

  • Dead Code = Unused (safe to delete).

  • Spaghetti Code = Used but messy (must be refactored).


Let’s take a Digital Lending use case (Loan Processing Service) and show both Dead Code and Spaghetti Code inside it.

🧑‍💻 Example: Loan Processing Service

1️⃣ Spaghetti Code (messy but used)

public class LoanProcessor {

    public String processLoan(Application app) {
        // Mix of validation, DB, business rules, notifications - all tangled

        if (app.getCustomerAge() < 18) {
            return "Rejected: Underage";
        } else {
            if (app.getCreditScore() < 650) {
                if (app.getSalary() > 50000) {
                    // apply special rule
                    return "Pending: Manual Review";
                } else {
                    return "Rejected: Low Credit Score";
                }
            } else {
                // inline DB call
                try {
                    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/loans");
                    PreparedStatement ps = conn.prepareStatement("INSERT INTO loans VALUES (?, ?)");
                    ps.setInt(1, app.getId());
                    ps.setString(2, "APPROVED");
                    ps.execute();
                    conn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                // inline email notification
                System.out.println("Sending approval email to: " + app.getEmail());

                return "Approved";
            }
        }
    }

    // Dead code below (never used anywhere)
    public void legacyLoanRule(Application app) {
        System.out.println("Old scoring model: Not used anymore!");
    }
}

2️⃣ Where’s the Problem?

  • Spaghetti Code

    • processLoan does everything: validation, DB, business rules, notification.

    • Nested if-else = hard to extend.

    • No separation of concerns.

  • Dead Code

    • legacyLoanRule() is never called.

    • Still in the class, but contributes nothing.

    • Confuses developers (“Should I call this?”).

3️⃣ Refactored (Clean Code)

public class LoanProcessor {

    private final LoanValidator validator;
    private final LoanRepository repo;
    private final NotificationService notifier;

    public LoanProcessor(LoanValidator validator, LoanRepository repo, NotificationService notifier) {
        this.validator = validator;
        this.repo = repo;
        this.notifier = notifier;
    }

    public String processLoan(Application app) {
        validator.validate(app);
        String status = evaluateLoan(app);
        repo.save(app.getId(), status);
        notifier.send(app.getEmail(), status);
        return status;
    }

    private String evaluateLoan(Application app) {
        if (app.getCustomerAge() < 18) return "Rejected: Underage";
        if (app.getCreditScore() < 650 && app.getSalary() <= 50000) return "Rejected: Low Credit Score";
        if (app.getCreditScore() < 650) return "Pending: Manual Review";
        return "Approved";
    }
}

✅ Now:

  • Spaghetti removed → responsibilities are split into validator, repo, notifier.

  • Dead code deleted → no unused legacy methods cluttering code.


Let’s simulate how SonarQube (or any static analysis tool) would flag Dead Code and Spaghetti Code in a Digital Lending Service.


🔎 Example: SonarQube Report for LoanProcessor.java

1️⃣ Spaghetti Code Findings

Rule: Cognitive Complexity > 15
File: LoanProcessor.java
Method: processLoan(Application)
Line: 5
Severity: MAJOR
Message: Method has a Cognitive Complexity of 35 which is greater than 15 authorized. Refactor this method to reduce its complexity.

Rule: Long Method
File: LoanProcessor.java
Method: processLoan(Application)
Line: 5
Severity: MAJOR
Message: Method has 80 lines. Maximum allowed is 20. Split this method into smaller methods.

Rule: S1643 (Mixing concerns)
File: LoanProcessor.java
Method: processLoan(Application)
Line: 25
Severity: CRITICAL
Message: Avoid direct database and notification calls inside business logic. Use repository and service layers.

2️⃣ Dead Code Findings

Rule: Unused Method
File: LoanProcessor.java
Method: legacyLoanRule(Application)
Line: 60
Severity: MINOR
Message: Remove this unused method or reference it. If it is intended for future use, mark it with @Deprecated.

Rule: Dead Store
File: LoanProcessor.java
Variable: unusedCounter
Line: 12
Severity: MINOR
Message: Remove this unused variable.

3️⃣ How Tools Differentiate

  • Spaghetti Code → flagged by rules around complexity, long methods, mixing concerns, code smells.

  • Dead Code → flagged by rules around unused methods, unreachable code, unused variables.

4️⃣ Team Workflow

  1. CI pipeline (e.g., Azure DevOps, GitHub Actions, Jenkins) runs SonarQube scan on each PR.

  2. Dashboard shows quality gates:

    • Max method complexity ≤ 15

    • Coverage ≥ 80%

    • No blocker or critical issues before merge

  3. Developers fix flagged code before merging → keeps repo clean.

✅ So in practice:

  • Spaghetti Code → You’d see “complexity too high” warnings.

  • Dead Code → You’d see “unused method/variable” warnings.

 
 
 

Recent Posts

See All
EA Day to Day Activity

🔹 Typical Day-to-Day Activities (Enterprise Architect – Digital Lending Program) 1. Start of Day – Communication & Prioritization Read &...

 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
  • Facebook
  • Twitter
  • LinkedIn

©2024 by AeeroTech. Proudly created with Wix.com

bottom of page