← Back to list

Understanding Large Codebases: Why AST Analysis Beats Asking an LLM

Last week, I inherited a 50,000 line Python monolith. You know the type of Django app from 2017 with six different authentication schemes…

Swupel in OSINT Team · 2026-01-29 16:53 · 76 claps · 6.9 min read
#programming #large-codebase #cybersecurity #data-visualization #newtechnologies
Open on Medium ↗
Wiki topics: LLM · Large Language Models VIS · Visual & Graphic Design 💻 · Programming 🌐 · Web Development 🔒 · Cybersecurity

Understanding Large Codebases: Why AST Analysis Beats Asking an LLM

Last week, I inherited a 50,000 line Python monolith. You know the type of Django app from 2017 with six different authentication schemes, import statements that circle back on themselves, and a helpers.py that’s somehow 3,000 lines long.

My first move? I asked an LLM to explain it.

The response was… fine. It gave me a plausible architectural overview, identified some patterns, and even flagged a few potential issues. But something felt off. When I drilled into specific files, the LLM’s confidence didn’t match reality. It would confidently describe a function’s behavior, but miss that it had a cyclomatic complexity of 23 and was nested seven layers deep in exception handlers.

That’s when it clicked: LLMs are great at natural language, but code isn’t natural language. It’s a formal grammar with deterministic structure. And if you want to understand structure, you need structural tools.

The Problem: Maintainability is About Mental Load, Not Line Count

When people talk about “technical debt” or “maintainability costs,” they’re really talking about one thing: how long does it take a human to load this codebase into their brain?

The bottleneck isn’t reading it’s mapping. You need to build a mental model of:

  • Which functions call which other functions

  • How deep the control flow nests (the dreaded “arrow code”)

  • Where the complexity actually lives

Reading code top-to-bottom is like trying to understand a building by walking through it blindfolded. You’ll eventually get there, but it’s slow and error-prone.

An LLM can summarize what it sees, but it’s fundamentally probabilistic. It’s predicting the next token based on patterns, not parsing the actual syntax tree. So when you ask “Is this code complex?” it might say “Yes, seems like a lot going on here,” but it can’t tell you that Function A has a cyclomatic complexity of 18 while Function B has a complexity of 3. That distinction matters when you’re deciding what to refactor.

The Alternative: Algorithmic Explanation via AST Analysis

Here’s the core insight:

Every piece of code you write gets parsed into an Abstract Syntax Tree (AST) by the interpreter.

That tree is the ground truth of your program’s structure.

If you visualize that tree, you can literally see complexity:

-Deep branches = heavily nested loops/conditionals

-Wide branches = functions with many decision points

-Tangled roots = circular dependencies

I’ve been experimenting with AST-based tooling (specifically building a visualizer for Python), and the difference is night and day. Instead of asking an LLM “Is this function complex?” I can see:

  • Cyclomatic complexity: How many execution paths exist? (Calculated by counting decision points: if, for, while, try/except, etc.)

  • Nesting depth: How many layers deep does this code go?

  • Dependency graph: Which files import which files? Where’s the “god object” that everything depends on?

Example: The Depth Chart

Depth Chart of a 30 line Python File

Depth Chart of a 30 line Python File

One feature I found particularly useful is a depth-over-sequence chart. Imagine a line graph where:

  • The X-axis is “lines of code, in order”

  • The Y-axis is “nesting depth”

Color-code it:

  • Green = Low nesting (depth 0–2)

  • Yellow = Moderate nesting (depth 3–4)

  • Red= Deep nesting (depth 5+)

When you run this on a codebase, problem areas light up like a heatmap. I ran it on that helpers.py file I mentioned — 80% of it was red. I didn’t need to read a single line to know: This is where the bugs are hiding.

Example: Dependency Graphs

Dependency Graph of FastAPI

Dependency Graph of FastAPI

Another thing AST analysis handles well: import resolution.

I ran a dependency graph on the entire project and immediately spotted a module that 47 other files imported. It was a 2,500-line utils.py with everything from database helpers to string formatting to API clients.

An LLM might say “This file seems to be a utility module.” True, but useless. The AST tool told me: “This is a central dependency. If you change this, you’re impacting 94% of your codebase.”

That’s actionable.

A Deeper Look: What Is Cyclomatic Complexity and Why Should You Care?

Let me unpack this metric because it’s one of the most useful signals you can extract from AST analysis.

Cyclomatic complexity measures the number of independent paths through a function’s code. It was developed by Thomas McCabe in 1976, and it’s still relevant because it correlates strongly with bug density and maintenance effort.

Here’s how it works:

  • Start with a base complexity of 1

  • Add +1 for every decision point: if, elif, for, while, try/except, and, or

  • The result is the number of test cases you’d need to achieve full branch coverage

Example: Simple Function

def validate_user(user):
  if not user:
    return False

  if user.age < 18:
    return False

  return True

Complexity: 3 (1 base + 2 if statements)

Example: Complex Function

def process_payment(user, amount, method):

  if not user or not user.is_active:
     raise ValueError(“Invalid user”)

  if method == “credit_card”:

    if user.credit_score < 600:
      return apply_for_approval(user, amount)

    elif amount > user.credit_limit:
      return request_limit_increase(user, amount)

  else:
    return charge_card(user, amount)

  elif method == “paypal”:

    if not user.paypal_linked:
      return link_paypal_account(user)

    return charge_paypal(user, amount)

  elif method == “bank_transfer”:

    if user.country not in SEPA_COUNTRIES:
      return initiate_wire_transfer(user, amount)

    return initiate_sepa_transfer(user, amount)

  else:
      raise ValueError(“Unknown payment method”)

Complexity: 11 (1 base + 10 decision points)

Why This Matters

Research (like the original McCabe paper and later studies from NASA and Microsoft) has shown:

  • Complexity 1–10: Low risk, easy to maintain

  • Complexity 11–20: Moderate risk, needs monitoring

  • Complexity 21+: High risk, strong candidate for refactoring

Functions with complexity >10 are statistically more likely to contain bugs. Not because “high complexity = bad code,” but because humans struggle to hold that many execution paths in working memory.

When you’re debugging at 2am, you want functions you can reason about without a flowchart.

The AST Advantage

An LLM can’t reliably calculate this. It might estimate “this looks complicated,” but it won’t give you the number. AST analysis counts the nodes deterministically — no guessing.

If you’re curious to try this on your own code, tools like https://ast-visualizer.com can parse your Python files and show you both the complexity scores and visual representations of where the complexity lives in your codebase.

The Workflow: Using AST Analysis to Cut Through Complexity

Here’s how I now approach large, unfamiliar codebases:

1. The Satellite View: Dependency Graph

Dependency Graph of Deepseek

Dependency Graph of Deepseek

First, I visualize the entire project’s import structure as a network graph.

Goal: Identify bottleneck files — modules that everything else depends on.

What I Look For:

  • Files with high “in-degree” (lots of files import them) = refactor candidates

  • Circular dependencies = architectural smell

Why This Beats LLMs: An LLM can tell you “This looks like a core module,” but the graph shows you exactly which 47 files import it. That’s the data you need to decide whether to break it apart or leave it alone.

2. The Terrain Map: Complexity Heatmap

Radial AST visualization for a file

Radial AST visualization for a file

Next, I run complexity analysis on the highest-traffic files.

Goal: Find the “red zones” — functions with high cyclomatic complexity or deep nesting.

What I Look For:

  • Functions with complexity scores >10 (industry rule-of-thumb threshold)

  • Nesting depth >5 (humans struggle to track context past ~4 levels)

Why This Beats LLMs: An LLM might say “This function is doing a lot.” AST analysis says “This function has 8 decision points, nests 7 layers deep, and touches 12 different variables. Complexity score: 16.” Now you have a concrete refactoring target.

3. The Microscope: Individual Function AST

Two functions represented in Linear Tree form

Two functions represented in Linear Tree form

Finally, for the gnarliest functions, I visualize the AST itself as a tree diagram.

Goal: Understand the exact structure — what’s inside those nested conditionals?

What I Look For:

  • Asymmetric branches (one if branch is way deeper than the else) = hidden edge cases

  • Repeated patterns (same code in multiple branches) = duplication to extract

Why This Beats LLMs: The tree view shows you the shape of the logic. You can literally see where the function branches and how balanced those branches are. That’s hard to convey in natural language.

The Honest Trade-off: When to Use Which Tool

I’m not saying “never use LLMs.” I use them constantly. But I’ve learned to use them for the right thing:

Use LLMs for:

  • Generating boilerplate

  • Explaining what a piece of code does (the semantics)

  • Suggesting refactorings once you’ve identified the target

  • Writing tests

Use AST Analysis for:

  • Understanding structure (the syntax and architecture)

  • Measuring complexity objectively

  • Finding refactoring targets

  • Mapping dependencies

Think of it this way: LLMs are great writers, but terrible cartographers. If you need a summary, ask an LLM. If you need a map, use structural analysis.

Closing Thought: Don’t Throw Away the Algorithms

We’re in this weird moment where AI can write entire functions for us, and it’s easy to think “classical tools are obsolete.”

They’re not.

Parsing, graph theory, and complexity metrics aren’t sexy, but they’re deterministic. They don’t hallucinate. They don’t “seem” to find a problem — they measure it.

You wouldn’t debug a segfault by asking an LLM to guess where the null pointer is. You’d use a debugger. Same principle applies to understanding code structure.

The best approach? Cyborg mode. Use AST analysis to find the problem. Use an LLM to help fix it.

Because you wouldn’t renovate a house based on someone’s description of the hallway. You’d look at the blueprints first.

— -

If you’re curious about AST-based analysis, Python’s built-in ast module is a great place to start. For visualization, there are a few open-source projects that parse the AST and render it as interactive graphs using D3.js or similar libraries. The key insight is that the structure already exists — you just need to make it visible.


메타데이터
post_id
b0d60fc99e65
slug
understanding-large-codebases-why-ast-analysis-beats-asking-an-llm-b0d60fc99e65
url
https://osintteam.blog/understanding-large-codebases-why-ast-analysis-beats-asking-an-llm-b0d60fc99e65
canonical_url
https://osintteam.blog/understanding-large-codebases-why-ast-analysis-beats-asking-an-llm-b0d60fc99e65
author_url
https://medium.com/@swupel
status
ok
fetched_at
2026-08-01 00:47:05