Code graphs, language servers (LSP), and ctags all index your code, but they answer different questions. ctags gives you fast "jump to this symbol's definition." LSP gives an editor live, per-file language intelligence — completion, hover, rename. A code graph like CGraph builds a persistent, whole-project model of how things relate, so you can ask what a change touches or how two symbols connect. None replaces the others; they sit at different points on the same spectrum.
The one-line version
- ctags — a symbol index. Where is
Xdefined? Cheap, portable, editor-agnostic. - LSP — a language server per language, feeding one editor live semantics for the file you're in.
- Code graph — a persistent, language-agnostic model of the whole project's relationships, built for querying and for feeding coding agents.
If you're editing code by hand, LSP and ctags earn their place every keystroke. If you're asking structural questions about the whole repo — or wiring a codebase into an AI agent — a code graph answers a question the other two don't.
What each one actually is
ctags
ctags scans source and emits an index of symbol definitions — a tags file mapping names to file and line. It has been around for decades, supports a long tail of languages via regex patterns, and needs no running process: generate the file, and any editor that reads it can jump to a definition instantly. Its model is deliberately shallow. It knows where a symbol is defined; it does not resolve which of three same-named functions you actually meant, and it does not track who calls what. That shallowness is also its strength — it's fast, dependency-free, and works everywhere.
LSP
The Language Server Protocol standardizes how an editor talks to a per-language "server" that understands that language's semantics. The server parses and type-checks as you edit and answers editor-centric requests: completions, hover types, go-to-definition, find-references, diagnostics, rename. It is precise because it does real semantic analysis, and it is interactive because it re-analyzes as you type. The tradeoffs follow from that design: you run a separate server per language, the intelligence is scoped to what an editor session needs, and there's no single durable artifact you can query outside the editor or hand to another tool.
Code graphs
A code graph treats the codebase as what it already is — files import files, functions call functions, symbols reference symbols — and makes that graph explicit and queryable. CGraph extracts the structure once into a deterministic graph (the same source tree always produces the same graph), resolves imports and calls across files, and serves that graph to tools and agents. Because the model is language-agnostic and whole-project, it can answer relationship questions that span files and even span languages: the transitive blast radius of a change, the shortest path between two symbols, a budgeted neighborhood around a node.
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 (the tags file) | No | Yes |
| Budgeted context for an LLM | No | No | Yes |
| Runs with no resident process | Yes | No | Yes (one-shot CLI) |
The honest reading of that table: LSP wins the interactive-editing rows — nothing beats a type-aware language server for completion, hover, and rename while you type. ctags wins on simplicity and reach — zero dependencies, one portable file, works in any editor for a huge range of languages. The code graph wins the whole-project structural rows — transitive impact, paths between symbols, one model across languages, and context sized to an LLM's budget.
Where they overlap (and it's fine)
All three can answer "where is X defined," so there's genuine overlap at the shallow end. That's not wasted effort — it means you rarely need all three for the same task. The distinction sharpens as questions get more structural:
- Find references is table stakes for LSP within a single language. A code graph does it too, but its real edge is the transitive version — not just direct callers, but everything reachable from a change (see Impact analysis in ~10ms).
- Precision favors LSP inside a file, because it type-checks. A code graph resolves statically across the project, which means dynamic dispatch, reflection, and runtime wiring produce some approximate edges — a tradeoff CGraph makes deliberately in exchange for a reproducible, diffable graph.
- Portability favors ctags and the code graph — both leave a file behind. CGraph writes its graph to
cgraph-out/and does not modify your source, so the artifact can be diffed, exported, or fed to another tool.
When to reach for which
Reach for LSP when a human is editing and wants live, type-aware help in one language at a time. It is the wrong tool for "analyze the whole repo in a script" — it's built around an editor session, not a batch query.
Reach for ctags when you want dependable go-to-definition with no setup, no daemon, and broad language coverage — especially in lightweight editors or over SSH. It is the wrong tool when you need to know relationships, not just locations.
Reach for a code graph when the question is structural and whole-project: what breaks if I change this signature, how do these two modules connect, or — increasingly — give an AI coding agent a persistent model of the repo instead of making it grep. This last case is where CGraph is pointed. A coding agent that reads code by grepping re-derives the codebase's structure on every turn; a warm graph lets it ask precise questions and get precise answers, spending fewer tokens rediscovering the codebase and more on the task. (Those token figures are from a self-run benchmark on CGraph's own repo in a Debug build — read the caveats there before quoting any single number.)
The takeaway
These are not competitors so much as tools tuned for different questions. ctags answers where, instantly and everywhere. LSP answers what does this mean, right here, as I type. A code graph answers how does this all fit together, and what does a change touch — and it's the only one of the three built to hand that understanding to an AI agent within a token budget.
If your work is interactive editing, keep LSP and ctags. If it's whole-project structure, or feeding a codebase to a coding agent, add a code graph. To see what that looks like in practice, read the MCP tools CGraph gives your coding agent or the code on GitHub.