Semantic Code Search: Why Intent Finds What Keywords Miss

Listen to this article Download MP3

Grep is fast and precise. It has served developers for decades. On a modern codebase, it routinely fails to find the thing you are actually looking for.

Not because the thing does not exist. Because the thing is named something else.

The function handling user authentication might be called validate_entity_token. The billing module might live inside something named core_ops. The caching layer might be split across four files, none of which contain the word "cache." This is what happens when codebases grow through teams, time, and schema changes. The code describes what someone intended in 2021 using vocabulary that made sense in 2021. The developer searching in 2025 does not know that vocabulary exists.

Grep finds the exact string you type. Semantic code search finds what you mean.

The Vocabulary Gap

Text search operates on one assumption: the word you want is the word in the code. That assumption breaks in predictable ways, and the breaks are not exotic edge cases.

Internal terminology evolves. A concept called "entity" becomes "user," then "member," then "account," depending on which product pivot arrived first. The code does not always catch up. Functions written during each naming era retain their original vocabulary, and a search for "account authentication" returns nothing because the relevant function was written when users were still called entities.

Developers also name things after what they do mechanically rather than what they represent in the business domain. A rate limiter might be token_bucket_enforcer. A fraud detection check might be anomaly_threshold_gate. Searching for rate limiting behavior means searching for a concept. The codebase contains implementations. Those vocabularies rarely align exactly.

Feature logic is rarely contained in one place, either. The "payment retry" flow might touch a scheduler, a webhook handler, a database model, and two utility functions. Keyword search finds files that contain the word "retry." It does not find the coordinated behavior those files produce in sequence.

Abbreviations compound all of it. Identifiers like usr_auth_svc, txn_proc, or cfg_mgr carry meaning that is invisible to anyone who was not present when the naming convention was established. A search for "transaction processor" finds nothing. The expanded form was never written into the code.

Semantic code search handles all of this. It is not looking for strings. It is looking for meaning.

How Embeddings Work

Every piece of code can be converted into a numerical representation: a vector that encodes the meaning of that code as understood by a trained language model. This is an embedding.

The embedding process is not counting keywords. It is capturing context. A function that validates user credentials will produce an embedding similar to one that authenticates a session token, even if the two functions share no words in common. Both describe the same intent. The embedding space reflects that.

When a developer types "where is authentication handled," that query also becomes a vector. The search engine finds code vectors most similar to the query vector using vector similarity. Cosine similarity is the standard method: it measures the angle between two vectors in high-dimensional space. Small angle means similar meaning.

The result is a ranked list of code locations ordered by semantic relevance, not text match.

Language models learn associations between concepts from enormous amounts of text. When trained on code and documentation, a model builds a representation where "authentication," "credential validation," and "session token check" end up nearby in the vector space, because they appear in similar contexts throughout the training data. The embedding captures that learned proximity. Intent-based search falls out of it naturally.

What Developers Actually Experience

A developer joins a team and needs to understand how the system handles errors in background jobs. Grep for "error handling" returns several hundred results across the codebase. Most are logging calls and exception imports. A semantic search for "background job error handling" returns the retry logic, the dead letter queue handler, and the alerting integration. The difference is not cosmetic. It is hours.

A developer is debugging a case where user data appears in a context it should not. The problem is data leaking across tenant boundaries. Keyword search for "tenant" finds the tenant model. Semantic search for "data isolation between tenants" finds the middleware that is supposed to enforce it, the places where it was bypassed, and the query builder method that conditionally applies tenant filters. The actual problem surface, not just the concept.

Context engineering for developers has become a real discipline in the last year, and this is exactly the problem it addresses. Getting an AI coding assistant to help requires giving it the right context. The right context means finding the right code. On a large codebase, keyword search alone cannot reliably do that.

Where Code Index MCP Tools Fit

The Model Context Protocol has made this plumbing explicit. A code index MCP exposes semantic search results directly into an AI assistant's context window. When a developer asks their assistant how billing retry works, the assistant can query the semantic index and receive ranked, relevant code rather than asking the developer to paste files manually.

Code analysis MCP tools extend this further, combining semantic retrieval with structural understanding: call graphs, dependency chains, and scope analysis layered on top of semantic results. Tools like ref.tools MCP and strands MCP clients are addressing the same underlying problem from slightly different angles: how to give an AI assistant access to the right slice of a large codebase without filling the context window with noise. Code router architectures sit at the top of this stack, directing queries toward the appropriate retrieval strategy based on what the query is actually asking for.

This is also where semantic search connects directly to code review. Ripple code review, the concept that changing one function should trigger inspection of semantically related code elsewhere, only works if you can find the related code efficiently. A function that shares purpose with the one being modified might not import it, might not reference it by name, and might live in a completely different module. Semantic similarity surfaces it anyway.

The Indexing Problem

Generic embeddings, trained on public code, do not understand the language your team developed. They do not know that bx_handler means the billing exception handler, or that v2_schema predates the current data model by four years. Search with generic embeddings and you get generic results. Useful for common patterns, imprecise for domain-specific ones.

Custom embeddings trained on your actual codebase fix this. The internal vocabulary, naming conventions, and component relationships all become part of the embedding space. Search precision improves substantially when the model has been trained on the codebase it is searching.

What remains unsolved at the operational level is freshness. Embeddings represent the code as it was when they were generated. A codebase that changes frequently will drift from its index. The search results go stale. Teams that treat indexing as a one-time task eventually find semantic search returning code that was refactored months ago. Automated reindexing on change detection is available. Most teams do not set it up.

The other open problem is distributed logic. Semantic search finds relevant files. It is less reliable when a feature is spread across many files that are individually unremarkable. Each file looks, in isolation, like generic infrastructure. The relevance signal only appears when you consider the group together. Graph-based retrieval, which follows import relationships alongside semantic similarity, helps with this. Combining graph traversal and semantic ranking in a way that reliably surfaces distributed behavior across a large codebase is still an active area of work.

Where This Goes

The developers who figure out semantic code search early are building a different kind of working relationship with their codebases. Not because the technology is remarkable on its own, but because finding code by intent changes which questions are worth asking.

Grep tells you where a string lives. Semantic search tells you where a concept lives. On a codebase touched by twenty people over four years, those are often not the same place.

Most teams are still running grep.

← Back to Blog