Blog/SEARCH & AI

Semantic Code Search: Find Code by What It Does

Giorgi Kenchadze

By Giorgi Kenchadze

Oct 24, 2026 · 6 min read

Grep finds names. Your team remembers behavior. Here's how semantic code search lets you find functions by describing what they do, in any language, with code.

Somewhere in your codebase there's a function that retries a request with exponential backoff. You know it exists because you reviewed the PR. You don't know what it's called, which service it lives in, or whether it was written in Rust, TypeScript or Python.

So you grep for "retry." You get 340 matches: log messages, config keys, a test named test_retry_disabled, and a comment that says "TODO: add retry." The function you wanted is called send_with_backoff, and it never shows up.

This is the gap between how code is searched and how people remember it. This post covers why text search falls short on code, how semantic code search works, and how to set it up with an API.

Why Grep Isn't Enough

Grep, ripgrep and most code search tools match characters. That's exactly right when you know the name: jumping to parseConfig or finding every caller of hashPassword is what they're built for.

It breaks down when you only know what the code does:

  • Names vary. The same job is called withRetry, send_with_backoff, fetch_with_backoff and retryable depending on who wrote it and when.
  • Words aren't behavior. "Retry" appears in hundreds of places that don't retry anything, and the function that does might not contain the word at all.
  • Languages split the search. A polyglot codebase means the same idea is written three ways with three syntaxes. A pattern that matches one rarely matches the others.
  • The query has to be in English. Identifiers are English-ish, so a developer who thinks in Spanish or Japanese has to translate before they can search.

The result is duplicate helpers. When finding the existing function takes longer than writing a new one, people write a new one.

How Semantic Code Search Works

Semantic search turns text into a vector, a list of numbers that represents meaning, and finds results by closeness instead of matching characters. (New to this? Start with Keyword vs Semantic Search.)

For code, the model that creates those vectors is trained on source code alongside natural language. It learns that a loop which sleeps for 100 << i milliseconds between attempts and a loop which sleeps for 2 ** i * 100 are the same idea, and that both match the phrase "retry with exponential backoff."

That gives you three things grep can't:

  1. Search by behavior. "Show file size like 2.4 MB" finds formatBytes(n), even though none of those words appear in it.
  2. Search across programming languages. One query returns the Rust, TypeScript and Python versions of the same function, ranked together.
  3. Search in any human language. "hash a password," "تشفير كلمة المرور" and "パスワードをハッシュ化する" all find hashPassword(plain). This is the same shared vector space that powers multilingual semantic search, applied to code.

Setting It Up

With Vecstore, you insert code the same way you insert any other text. There's no parser to configure and no setup per language.

Start by inserting functions. Splitting by function usually gives better results than inserting whole files, because each result points at one thing that does one job:

const BASE_URL = 'https://api.vecstore.app/api';

const snippets = [
  {
    file: 'src/client.rs',
    language: 'rust',
    code: `fn send_with_backoff(req: &Request, max: u32) -> Result<Response> {
    for i in 0..max {
        if let Ok(res) = req.send() { return Ok(res); }
        sleep(Duration::from_millis(100 << i));
    }
    Err(Error::Timeout)
}`,
  },
  {
    file: 'src/utils/format.ts',
    language: 'typescript',
    code: `export function formatBytes(n: number) {
  const units = ['B', 'KB', 'MB', 'GB'];
  let i = 0;
  while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
  return n.toFixed(1) + ' ' + units[i];
}`,
  },
];

for (const snippet of snippets) {
  await fetch(`${BASE_URL}/databases/${DB_ID}/documents`, {
    method: 'POST',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      text: snippet.code,
      metadata: { file: snippet.file, language: snippet.language },
    }),
  });
}

Then search it in plain words:

async function searchCode(query) {
  const res = await fetch(`${BASE_URL}/databases/${DB_ID}/search`, {
    method: 'POST',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ query, top_k: 5 }),
  });

  const { results } = await res.json();
  return results;
}

await searchCode('retry a request with exponential backoff');
await searchCode('show file size like 2.4 MB');
await searchCode('reintentar una petición si falla');

The first query returns send_with_backoff. The second returns formatBytes. The third is Spanish and still returns the Rust retry loop. The file and language metadata come back with each result, so you can link straight to the source.

What People Build With It

Internal code search. Index your repositories on every merge and give the team a search box that answers "do we already have something that parses a date string?" before anyone writes a fourth version.

Context for AI tools. Coding assistants are only as good as the code they can see. Before the model writes anything, search for the functions related to the task ("where do we check auth tokens") and put them in the prompt. The model reuses your helpers instead of inventing new ones.

Snippet libraries and docs. If you publish examples, people arrive with a problem, not a function name. "Upload a file to S3" should land on the example that does it, whatever it's called.

Where It Still Needs Care

Exact lookups are still grep's job. Finding every caller of hashPassword or every use of a deprecated API is a string match. Use semantic search for "what does this," and text search for "where is this."

Chunk size matters. A 2,000 line file is about many things, so its vector is about none of them in particular. Insert functions or small groups of related functions, and keep the file path in metadata.

Keep the index in sync. Code changes constantly. Re-insert changed functions on merge and delete removed ones, or results will point at code that no longer exists.

Filter with metadata. Store the repo, path, language and owner with each snippet. Then "parse a date string" can be narrowed to the service you're working in instead of the whole company.

The Bottom Line

Text search ties results to names, and names are the part of code people remember least. Semantic code search ties results to behavior, so the question you actually have, "where's the thing that does this?", becomes the query. One database holds every programming language, and anyone on the team can search it in their own words.

See it in action on the Code Search page, or try Vecstore for free. 100 credits on signup, no credit card. Insert a few of your own functions and search for them without using their names.

← Back to all posts

Better search for your product without the engineering overhead.

85M+ searches powered by Vecstore this year

Sign up for Vecstore
Start for Free

100 Free credits. No credit card required.