Semantic Code Search is a technique for finding code in repositories by meaning, not just by keywords. It matters when developers need to locate relevant code across large, heterogeneous codebases—especially when the exact function name, variable, or file path is unknown, or when the codebase has evolved beyond simple text matching. It’s essential during onboarding, refactoring, bug triage, and feature implementation.

Finding Code by Intent

When you search for “find all places where user login is validated,” you expect to return code that checks credentials, handles session tokens, and validates JWTs—even if the function is called verify_auth, check_login, or authenticate_user. Semantic search uses embeddings to map code snippets, functions, and files into a shared vector space, so that semantically similar code is close in space.

This is not a simple grep or ripgrep. It’s a query that understands code structure, naming conventions, and control flow. The query is not just a string—it’s a natural language prompt, a code snippet, or a combination of both.

Querying with Natural Language

A natural language query like "find functions that process user signup data" returns code that parses registration forms, validates email formats, sends welcome emails, and creates user profiles—even if no function is explicitly named process_signup.

The query is encoded into a vector using a pre-trained model (e.g., CodeBERT, StarCoder, or Sentence-BERT). This vector is then used to search a precomputed index of code embeddings.

curl -X POST https://search.code.local/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "find functions that process user signup data",
    "limit": 10,
    "include_code": true,
    "include_context": true
  }'

The response includes matching code snippets, their file paths, and surrounding context:

{
  "results": [
    {
      "file": "src/auth/signup.py",
      "line": 42,
      "score": 0.94,
      "code": "def process_signup(data: dict) -> User:\n    email = data.get(\"email\")\n    password = data.get(\"password\")\n    if not validate_email(email):\n        raise ValueError(\"Invalid email\")\n    user = User(email=email)\n    user.set_password(password)\n    user.save()\n    send_welcome_email(user)\n    return user",
      "context": [
        "def validate_email(email: str) -> bool:\n    pattern = r\"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$\"\n    return re.match(pattern, email) is not None"
      ]
    }
  ]
}

The include_context flag returns surrounding code—typically the nearest 10 lines before and after the matched snippet—so the developer can understand the broader scope.

Querying with Code Snippets

You can also search for code that is semantically similar to a given code snippet. This is useful when you’ve found a good implementation and want to find similar patterns elsewhere.

For example, given a function that creates a JSON API response with pagination, you want to find other places in the codebase that do the same.

def api_response(data, total, page, per_page):
    return {
        "data": data,
        "pagination": {
            "total": total,
            "page": page,
            "per_page": per_page,
            "pages": (total + per_page - 1) // per_page
        }
    }

You submit this code as the query:

curl -X POST https://search.code.local/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "code_snippet": "def api_response(data, total, page, per_page):\n    return {\n        \"data\": data,\n        \"pagination\": {\n            \"total\": total,\n            \"page\": page,\n            \"per_page\": per_page,\n            \"pages\": (total + per_page - 1) // per_page\n        }\n    }",
    "limit": 8,
    "mode": "code_similarity"
  }'

The mode: code_similarity flag tells the system to treat the input as a code snippet and compute its embedding directly, rather than encoding the full function as natural language. The system uses a code tokenizer that preserves syntax, variable names, and structure—so a for loop with nested if statements is not reduced to a bag-of-words.

Combining Natural Language and Code

You can combine both approaches: a natural language query that includes a code snippet as a reference.

{
  "query": "find all implementations of user session management",
  "code_snippet": "def create_session(user: User) -> str:\n    token = generate_jwt(user)\n    cache.set(f\"session:{user.id}\", token, timeout=3600)\n    return token",
  "limit": 12,
  "include_code": true,
  "merge_strategy": "weighted_sum",
  "weights": {
    "natural_language": 0.6,
    "code_similarity": 0.4
  }
}

The merge_strategy: weighted_sum computes the final score by combining the natural language score and code similarity score using the provided weights. This allows fine-grained control over how much each signal contributes.

Building and Maintaining the Code Index

Semantic code search depends on a precomputed index of code embeddings. This index must be kept up to date as code evolves.

Indexing Code from Version Control

Code is indexed from Git repositories. The process starts with a git clone of the repository, then extracts every function, class, and file.

A common indexing pipeline uses ripgrep to extract code units and sentence-transformers to compute embeddings.


rg -l "def |class |def " --files-with-matches src/ | xargs -I{} \
  python -c "
import os
from pathlib import Path
import json
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')

def extract_snippets(file_path):
    with open(file_path, 'r') as f:
        lines = f.readlines()
    # Extract function and class definitions
    snippets = []
    start = None
    for i, line in enumerate(lines):
        if line.strip().startswith(('def ', 'class ')):
            start = i
        elif start is not None and line.strip().startswith(('def ', 'class ')) or i == len(lines) - 1:
            end = i
            content = ''.join(lines[start:end])
            snippets.append({
                'file': file_path,
                'start_line': start + 1,
                'end_line': end,
                'content': content
            })
    return snippets

# Main
repo_root = Path('/code/repo')
files = [f for f in repo_root.rglob('*') if f.is_file() and f.suffix in ['.py', '.js', '.ts', '.go', '.rs']]
all_snippets = []

for f in files:
    all_snippets.extend(extract_snippets(f))

# Compute embeddings
texts = [s['content'] for s in all_snippets]
embeddings = model.encode(texts)

# Save to disk
index = []
for snippet, emb in zip(all_snippets, embeddings):
    index.append({
        'file': snippet['file'],
        'start_line': snippet['start_line'],
        'end_line': snippet['end_line'],
        'content': snippet['content'],
        'embedding': emb.tolist()
    })

with open('/index/code-index.json', 'w') as f:
    json.dump(index, f)
"

This script produces a JSON file with each code unit and its embedding. The index is then uploaded to a vector database like FAISS, Milvus, or PostgreSQL with the pgvector extension.

Updating the Index Incrementally

The index is not rebuilt from scratch every day. Instead, it’s updated incrementally using git log.

# Get commits since last index update
git log --since="2024-03-01" --name-only --pretty=format:"" origin/main..HEAD

For each file changed in the commit range, the system checks if the file’s functions have changed significantly—by comparing the new version of the file against the old version using diff.

If the changes affect a function’s signature, body, or structure, the function is re-embedded and the index is updated.

The update process is idempotent: if the same code snippet appears in multiple commits, the latest version replaces the old one.

Managing Index Performance and Storage

The index must be optimized for query latency. Large codebases can have millions of code units.

Each code snippet is stored with metadata: file path, line numbers, language, and a version hash of the file.

When querying, the system retrieves the top-k vectors using a similarity metric (typically cosine similarity) and applies a filter.

{
  "query": "find all functions that handle payment processing",
  "filters": [
    { "field": "language", "value": "python" },
    { "field": "file", "value": "src/payment/" }
  ],
  "limit": 20,
  "min_score": 0.7
}

The min_score threshold ensures that only high-confidence matches are returned. Queries under this threshold are discarded or re-ranked.

A common failure mode is that the index grows too large, causing slow query times. The system must periodically re-index, but not all code units are equally important.

Failure Modes and Edge Cases

  • Embedding drift: Over time, the model used to compute embeddings becomes outdated. A query from January returns different results than the same query in June, even with identical code. This happens when the model is trained on a different data distribution.
  • Code snippet tokenization mismatch: A function with a for loop in Python is tokenized differently than one with for ... in in JavaScript. The model expects a certain structure, but the code snippet deviates—e.g., using async def instead of def. The embedding is less accurate.
  • Incomplete context: When returning context around a match, the system may include code from multiple files. But if a function is defined in utils.py and called from main.py, the context may be split across two files, yet the query expects a single file. The system must align the context to a single file or provide cross-file references.
  • Score thresholding: Queries with min_score return only high-quality results, but low-scoring matches are silently dropped. A developer may miss a relevant function because it scored 0.69 instead of 0.70.
  • Language-specific features: A function that uses async/await in Python may be misclassified as a synchronous function if the embedding model was trained on non-async code. This causes mismatched results for asynchronous code.
  • Indexing delays: Code is committed and pushed, but the index update takes 15 minutes. During that time, a developer searches for a new function and gets outdated results. The system must track index freshness and report a last_updated timestamp in the response.

Using Semantic Search in Daily Workflows

Semantic code search is most effective when integrated into the developer’s workflow.

IDE Integration

A developer opens a file and selects a function. They press Cmd+Shift+Q to search for similar implementations.

The IDE sends the function’s code to a semantic code search service and displays results in a side panel.

The IDE can also allow filtering by file, language, or function name.

CI/CD Integration

In a pull request, the CI system runs a semantic search on the code changes to suggest related code.

jobs:
  semantic-search:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          curl -X POST https://search.code.local/v1/query \
            -H "Content-Type: application/json" \
            -d '{
              "query": "refactor this function to use async processing",
              "code_snippet": "'$PR_BODY'",
              "limit": 5,
              "include_code": true
            }' \
            -o suggestions.json
      - run: |
          echo "Suggested related code:"
          jq -r '.results[].file + \":\" + .line + \" - \" + .code' suggestions.json

This helps reviewers understand the broader context and suggests further improvements.

Monitoring and Alerting

A monitoring system watches for low-quality search results—e.g., queries with average score below 0.5.

It sends alerts when the index has not been updated for more than 30 minutes, or when the embedding model produces unexpected outputs.

An alert is triggered when a new code snippet is submitted but the top match is unrelated.

The system logs all queries and their performance, allowing teams to identify common search patterns and optimize the index.

Semantic code search transforms how engineers explore and understand code. It is not just a tool—it is a new way of thinking about code as a living, searchable, interconnected corpus.

This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.