# How Dead Code Detection Identifies Functions with Zero Callers in Codebase-Memory-MCP

> Learn how dead code detection finds functions with zero callers by analyzing call graphs and filtering entry points in your codebase memory MCP. Improve code quality and efficiency.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: how-to-guide
- Published: 2026-07-28

---

**Dead code detection identifies functions with zero callers by analyzing the CALLS relationship graph built during indexing, exposing virtual `in_degree` properties that count incoming call edges, and filtering out legitimate entry points like `main()` functions and public APIs.**

The **Codebase-Memory-MCP** engine implements dead code detection through a graph-based analysis pipeline that tracks every function invocation across your codebase. By treating function calls as directed edges in a graph stored in SQLite, the system can efficiently query for isolated nodes—functions that never receive incoming calls—while excluding false positives such as framework handlers and test entry points.

## Building the CALLS Graph for Dead Code Analysis

The foundation of dead code detection lies in the **CALLS relationship graph** constructed during the indexing phase.

### Parsing Function Invocations in pass_calls.c

During indexing, the pipeline stage **[`src/pipeline/pass_calls.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_calls.c)** walks the abstract syntax trees (ASTs) produced by Tree-Sitter to identify every function-call expression. For each call discovered, it creates a directed edge of type `CALLS` from the caller node to the callee node, storing these relationships in the SQLite backing store ([`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c)).

```c
/* Excerpt from pass_calls.c */
if (is_call_expression(node)) {
    int caller_id = get_node_id(caller);
    int callee_id = get_node_id(callee);
    cbm_edge_t edge = {
        .project = proj,
        .source_id = caller_id,
        .target_id = callee_id,
        .type = "CALLS"
    };
    cbm_store_add_edge(store, &edge);
}

```

Every detected call results in a directed edge that connects the calling function to its target, enabling the system to calculate reachability metrics.

### Virtual Degree Properties in cypher.c

The query engine exposes edge counts as virtual node properties through **[`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c)**. When a Cypher query requests `in_degree` or `out_degree`, the engine looks up pre-computed CALLS edge counts and injects them as ordinary JSON fields (see lines 2216–2222).

```c
/* Virtual properties handling – lines 2216-2222 */
if (store && (strcmp(prop, "in_degree") == 0 || strcmp(prop, "out_degree") == 0)) {
    int val = (strcmp(prop, "in_degree") == 0) ? in_deg : out_deg;
    yyjson_mut_obj_add_int(doc, row, prop, val);
}

```

This mechanism allows developers to query `in_degree` (the number of incoming CALLS edges) as if it were a native property of each function node.

## Filtering Out False Positives in Dead Code Detection

Not every function with zero callers represents truly dead code. The detector applies **smart filters** to exclude legitimate entry points that intentionally lack callers.

### Excluding Entry Points and Public APIs

The system recognizes several categories of functions that should not be flagged as dead, even when their `in_degree` equals zero:

- **`main()` functions** – Recognized by name patterns (`main`, `init`)
- **Route handlers and framework decorators** – Detected via language-specific conventions (e.g., FastAPI, Spring annotations)
- **Exported symbols** – Marked in the index as `exported` or `public`
- **Test functions** – Tagged with the `test` label

These exclusions are implemented in the query layer, where the `in_degree` filter combines with additional predicates to eliminate false positives.

### UI-Level Filtering in FilterPanel.tsx

The React component **[`graph-ui/src/components/FilterPanel.tsx`](https://github.com/DeusData/codebase-memory-mcp/blob/main/graph-ui/src/components/FilterPanel.tsx)** provides a "Show only dead code" toggle (line 218) that automatically constructs the appropriate query constraints.

```tsx
{/* FilterPanel.tsx – line 218 */}
<label>
  <input
    type="checkbox"
    name="showDead"
    onChange={toggleDeadCode}
  />
  Show only dead code
</label>

```

When activated, this component appends `in_degree = 0` to the current Cypher query while applying the entry-point exclusion rules.

## Querying for Dead Functions with Cypher

You can retrieve dead functions directly through Cypher queries that combine the virtual `in_degree` property with exclusion filters:

```cypher
-- Find functions that have no callers and are not exported
MATCH (f:Function)
WHERE f.in_degree = 0
  AND NOT f.exported
  AND NOT f.is_route_handler
RETURN f.name, f.file_path

```

A function is reported as **dead code** only when its `in_degree` is `0` and it matches none of the exclusion criteria defined in the query layer.

## Summary

- **Dead code detection** relies on the CALLS graph built by [`pass_calls.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/pass_calls.c) during AST parsing.
- The **`in_degree`** virtual property in [`cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/cypher.c) counts incoming call edges without storing redundant data.
- **Entry-point filters** exclude `main()` functions, route handlers, and public APIs from dead code reports.
- Both the **CLI** (`cbm search_graph`) and **web UI** ([`FilterPanel.tsx`](https://github.com/DeusData/codebase-memory-mcp/blob/main/FilterPanel.tsx)) support querying for zero-caller functions.

## Frequently Asked Questions

### What file creates the CALLS edges for dead code detection?

The pipeline stage **[`src/pipeline/pass_calls.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_calls.c)** creates CALLS edges by walking Tree-Sitter ASTs during the indexing phase. It identifies function call expressions and stores directed edges in the SQLite backing store via `cbm_store_add_edge()`.

### How does the query engine calculate in_degree for functions?

The query engine calculates `in_degree` dynamically in **[`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.c)** (lines 2216–2222). When a Cypher query requests this property, the engine counts the incoming CALLS edges for the node from the pre-computed graph stored in SQLite, returning the value as a virtual property.

### Why doesn't dead code detection flag main() functions?

The detector applies **smart filters** that recognize legitimate entry points by name patterns (`main`, `init`), framework conventions, export status, and test labels. These filters prevent functions that serve as program entry points or public APIs from being incorrectly identified as dead code.

### Can I filter dead code through the web interface?

Yes. The **[`FilterPanel.tsx`](https://github.com/DeusData/codebase-memory-mcp/blob/main/FilterPanel.tsx)** component in the graph UI provides a "Show only dead code" checkbox that automatically appends `in_degree = 0` constraints to your query while excluding entry points. You can also use the CLI command `cbm search_graph` with custom Cypher filters to retrieve dead functions programmatically.