← Back to blog
·4 min read

Code graphs vs LSP vs ctags: choosing a code intelligence layer

ctags answers where a symbol lives, LSP answers what code means as you type, and a code graph answers what a change touches. A decision table for all three.

code-intelligencedeveloper-toolingai-agents

ctags, the Language Server Protocol, and a code graph all index your code, but they answer different questions: ctags answers where is this defined, LSP answers what does this mean as I type, and a code graph answers how does this all fit together — and what does a change touch. Choosing between them is really choosing which question your tooling needs answered, and for AI coding agents the answer is usually the one the first two can't give.

Three tools, three questions

ctags scans source and emits a portable index of symbol definitions — a tags file mapping names to file and line. No running process, decades of editor support, a long tail of languages. Its model is deliberately shallow: it knows where a symbol is defined, not which of three same-named functions you meant, and not who calls what. That shallowness is also why it works everywhere.

LSP standardizes how an editor talks to a per-language server that does real semantic analysis as you edit: completion, hover types, find-references, diagnostics, rename. It is precise because it type-checks, and interactive because it re-analyzes as you type. The trade-offs follow from the design: one server per language, intelligence scoped to an editor session, and no durable artifact you can query from a script or hand to another tool.

A code graph makes explicit what the codebase already is — files import files, functions call functions — and keeps that whole-project model queryable. CGraph, our open-source engine, extracts the graph deterministically (the same tree always produces the same graph), resolves references across files and languages, and serves it to tools and agents from a resident daemon.

The decision table

Question ctags LSP Code graph
Where is X defined? Yes Yes Yes
Live completion / hover / rename as I type No Yes No
Type-aware precision within a file No Yes Partial
Who calls X (find references) No Yes (per language) Yes
What does changing X transitively break? No Partial Yes
Shortest path between two symbols No No Yes
One model spanning many languages Partial No Yes
A persistent artifact other tools can query Yes No Yes
Budgeted context for an LLM No No Yes

The honest reading: LSP wins every interactive-editing row — nothing beats a type-aware language server while a human types. ctags wins on simplicity and reach. The code graph wins the whole-project structural rows: transitive impact, paths between symbols, one model across languages, and context packed to an LLM's token budget.

The overlap at the shallow end — all three answer "where is X defined" — is fine; it means you rarely need all three for one task. Precision is the real trade: LSP type-checks inside a file, while a graph resolves statically across the project, so dynamic dispatch and runtime wiring produce some approximate edges — a trade CGraph makes deliberately in exchange for a reproducible, diffable artifact.

When to reach for which

Reach for LSP when a human is editing and wants live, type-aware help. Reach for ctags when you want dependable go-to-definition with zero setup — lightweight editors, remote shells. Reach for a code graph when the question is structural and whole-project: what breaks if I change this signature, how do these modules connect — or when the "user" is not a human at all.

That last case is the one that changed our ranking. An agent that navigates by search re-derives the repo's structure on every turn — the grep-and-read loop we measured in the token tax of grep-and-read — and it cannot open an LSP session or profit from live completions. A warm graph is the only layer of the three built to hand whole-project structure to an agent within a token budget, which is exactly how CGraph serves it over MCP.

Takeaway

These are complements, not competitors: keep LSP and ctags for humans editing, and add a code graph when the question is whole-project structure or the consumer is an AI agent. Deciding which layers your team's tooling — and your agents — should stand on is an architecture decision we help teams make.

Building something like this?