← Back
Home › Systems › AI applications

code-completion

Shows a suggestion of what a programmer is about to type as grey text at the cursor, produced fast enough to never block typing.

Copilot, Cursor Tab

Open the code-completion pagecode-completion Code context retrievalagentic-ai: Constrained decodingagentic-ai: Force LLM output to match a regex, JSON schema or grammar by masking, at every step, the tokens that would leave the language.›Prompt cachingagentic-ai: Reuse the attention keys/values of a repeated prompt prefix, so an agent's growing context is prefilled once and later turns pay only for the new tokens.›Incremental parsingcompilers: Reparse only the region an edit damaged and splice the new subtrees into the old tree, sharing everything else; O(edit) instead of O(file).›Language server protocolcompilers: One JSON-RPC conversation over a pipe lets any editor drive any language's analysis server; the cost is keeping a live, edited document in sync and answering before it changes.›Continuous batchingml-training: Schedule LLM inference per iteration, not per request; finished sequences leave and new ones join the running batch every step, which keeps the GPU full.›Fill in the middleml-training: FIM training and prompting: prefix/suffix/middle reordering with sentinel tokens for code infillKnowledge distillationml-training: Train a small student to match a large teacher's softened outputs (plus the hard labels) so it keeps most of the teacher's accuracy at a fraction of the cost.›Paged KV cacheml-training: Store each sequence's attention keys/values in fixed-size blocks mapped through a block table, like virtual memory pages; near-zero fragmentation and copy-on-write sharing.›Token samplingml-training: Turn a vector of scores into one chosen item with temperature, top-k, top-p, min-p, repetition penalties and a seed.›Speculative decodingml-training: A cheap draft proposes γ tokens, the big model verifies them in one pass, and rejection sampling keeps the target's exact distribution at 2-3x speed.›Token healingml-training: Backing up the last prompt token and constraining generation to avoid tokenization artifacts at boundariesWeight quantizationml-training: Store weights as 8/4-bit integers with per-group scales; decode is memory-bound, so 4-bit weights give about 3-4x tokens/s and fit 4x larger models.›A/B testing (online controlledexperiments)statistics: Randomly split units into arms by a salted hash, log exposure, compare metrics with a confidence interval, and guard the result with sample-ratio, guardrail and peeking checks.›Debouncing throttlingsystems: Coalescing rapid events: debounce (wait for quiet), throttle (max rate), and cancelling stale in-flight requestsLRU and CLOCKsystems: LRU evicts the least recently used entry via hash map plus linked list; CLOCK approximates it with a reference bit, so hits take no lock.›Code symbol indexingcompilers: Extract definitions and references per file, give each symbol a stable string id, and store symbol to occurrences, re-indexing only files whose content hash changed.›Embeddingsmachine-learning: A learned table mapping discrete ids to dense vectors: gather forward, sparse scatter-add backward, row-wise optimizers to keep huge tables cheap.›Hybrid search and rank fusionsearch-sort: Merge a keyword (BM25) result list and a vector (ANN) list into one ranking, by ranks (RRF) or by normalized scores (convex combination).›Trigram indexsearch-sort: Trigram posting lists accelerating regex and substring searchByte-pair encoding (BPE)tokenizationmachine-learning: Learn a subword vocabulary by repeatedly merging the most frequent adjacent pair; byte-level BPE encodes any input losslessly with 32k-200k tokens.›Finite state transducersearch-sort: Minimal acyclic FST mapping sorted keys to values; compact term dictionaries with prefix/fuzzy searchJSON Schema validationsecurity: Compile a JSON Schema once, then check every untrusted document against it, within hard size and depth limits, and report failures as JSON Pointers.›Fast hash functionsdata-structures: Non-cryptographic hashes built from 64×64→128-bit multiplies and xor-shifts; pick by key size, trust level and whether the hash is persisted.›Radix triedata-structures: Compressed prefix tree for path routing, domain matching and prefix lookupsLossless syntax treescompilers: Concrete syntax trees with trivia, green/red node split (Roslyn, rowan) for tools that rewrite codeParser error recoverycompilers: Recover from syntax errors via synchronization tokens and error nodes to keep a full treeRecursive-descent parsingcompilers: Hand-written top-down parser with one function per grammar rule; simple, fast, with full control over error messages and recovery.›+ 14 moreName resolutioncompilers: Bind every use of a name to its declaration with a scope stack, handling shadowing, hoisting and closure capture in one AST walk.›Protobuf wire formatnetworking: Protocol Buffers wire format: tags, varints, length-delimited fields and schema evolutionAdamWml-training: Adam's per-parameter adaptive steps with weight decay applied directly to the weights; the default optimizer for transformers and LLM pretraining.›Reverse-mode automaticdifferentiationml-training: Record the forward computation on a tape, then sweep it backwards once to get the gradient of a scalar loss for all inputs.›Inverted indexstorage: Map each term to a sorted, compressed list of the documents containing it; the core of full-text search and other fast set queries.›BM25 rankingsearch-sort: The standard lexical relevance score: IDF-weighted term frequency with saturation (k1) and length normalization (b); default in Lucene, Elasticsearch and SQLite FTS5.›Top-k selectionsearch-sort: Keep the k best of n items without sorting all n - bounded min-heap for streams, quickselect for arrays, threshold pruning to skip most items.›Sorted-list intersectionsearch-sort: Intersect sorted id lists by merging, galloping from the short list into the long one, and skipping whole compressed blocks, shortest list first.›Linear-time regular expressionmatchingsearch-sort: Match a regular expression in O(pattern x text) worst case by tracking all live parse positions at once, then cache the state sets as a lazily built DFA for speed.›Text normalizationmachine-learning: Rewrite messy text into one canonical form for matchers and models, or into spoken words for speech output (and back), keeping a map to the original offsets.›Text position encodingcompilers: Store byte offsets, convert to (line, column) on demand with a line-start table and binary search, and count columns in the unit the peer negotiated.›Hash consingdata-structures: Intern structurally equal immutable terms so equality is pointer comparison and sharing is maximal+ 5 moreBit packing and PFORcompression: Store blocks of integers with exactly b bits each and patch rare outliers separately (PFOR); the fastest integer codec family, billions of values per second.›Delta encodingcompression: Store differences between consecutive values instead of the values, turning sorted IDs, timestamps and counters into small numbers that pack into few bits.›Varint (LEB128)compression: Variable-length integers in 7-bit groups with a continuation bit; small numbers take 1 byte, the wire format of Protobuf, WebAssembly and DWARF.›Unicode text tokenization forsearchsearch-sort: Turn multilingual text into index terms: segment words (UAX 29 style, CJK bigrams), normalize, case-fold and accent-fold, identically at index and query time.›d-ary heapdata-structures: Array heap with d children per node (d = 4 typical): half the binary heap's depth, siblings in one cache line, faster priority queues.›Aho-Corasick multi-patternmatchingsearch-sort: Find all occurrences of many fixed patterns in one pass over the text, in time independent of the pattern count, using a trie with failure links.›SIMD substring searchsearch-sort: Find bytes or substrings by comparing 16-64 haystack positions per instruction against the needle's first and last byte, then verifying the rare candidates.›+ 1 moreSIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›

How it is built

Architecture overview

Inline code completion shows grey "ghost text" at the cursor, a guess at what the programmer is about to type, and accepts it with one key. It is a request loop with a latency budget of a few hundred milliseconds that must run on a fraction of keystrokes, never block typing, and show a suggestion only when it is probably right. A small code language model, trained to fill in the middle of a file given the text before and after the cursor, does the guessing. Around it sit the parts that decide quality: a context builder that picks what the model sees (the code around the cursor and relevant snippets from other files), a request manager that drops stale work, a serving layer that reuses computation across requests, and a filter that hides low-confidence suggestions. The one idea that makes it work: the model is only a third of the product. Most of the quality comes from the prompt you assemble, and most of the latency comes from the work you avoid: requests never sent, prompts already cached, tokens never generated. Acceptance data from users closes the loop.

Parts:

 keystroke ─► debounce ─► cancel older request ─► build prompt ─────────────► inference server
  (editor)     (wait)         (in flight)         prefix │ suffix │ snippets    ┌───────────────────┐
     ▲                                              ▲        ▲                 │ prefix cache hit? │
     │                                  open files, │        │ symbol index,   │ prefill ► decode  │
     │ ghost text                       recent edits│        │ syntax tree     │ (FIM model, short)│
     │                                              │        │                 └─────────┬─────────┘
 accept (Tab) ◄── show? ◄── filter, trim, syntax ◄─┴────────┴── completion ◄────────────┘
     │            (score)       check
     └──► log: shown, accepted, rejected ──► metrics, experiments, next model

How the parts fit together

Editor client

The editor talks to completion providers through its own API or, increasingly, through the Language server protocol: version 3.18 of the specification adds a textDocument/inlineCompletion request for exactly this ghost-text case, next to the older completion list that suits a dropdown of symbols. The LSP page covers the cost of keeping the server's copy of a live, edited document in sync; for inline completion the important contract is the request payload: document version, cursor position, trigger kind, and a way to cancel.

Three rules keep the client from hurting the user.

  • Wait before asking (debouncing-throttling, no page yet). Typing produces a request per keystroke; sending them all wastes compute and returns suggestions for text that no longer exists. Continue's documentation, for example, shows a configuration with a debounce delay of 250 ms and a model timeout of 150 ms (an example configuration, not a universal setting).
  • Cancel the previous request. When a new keystroke arrives, abort the in-flight call, and make the server stop generating, because tokens produced for an old cursor position are paid for and thrown away.
  • Never render a stale answer. Every response is checked against the current document version and cursor; if the user has typed characters that match the start of the suggestion, the client trims it instead of discarding it.

The client also owns the syntax-aware parts that need to be instant: an incremental parser such as tree-sitter re-parses only the edited region on every keystroke (its README: fast enough to parse on every keystroke, robust in the presence of syntax errors), which tells the context builder whether the cursor is inside a function, a string or a comment, and tells the filter whether a completion leaves the file parseable.

Context builder

The model is only as good as its prompt, and the prompt has a fixed budget (Continue's example configuration caps it at 1024 tokens, with fractions of it reserved for prefix and suffix). Three sources go in:

  • Prefix and suffix. Text before the cursor and after it, in the fill-in-the-middle format (fill-in-the-middle, no page yet). The format comes from training: a span from the middle of a document is moved to the end, so an ordinary left-to-right model learns to infill, and the study of this transformation by Bavarian et al. (2022) found it did not hurt left-to-right ability. The model is prompted with special sentinel tokens for prefix, suffix and "middle", then generates the middle. The suffix matters: without it the model repeats code that already follows the cursor or closes brackets that are already closed.
  • Related code from the repository (code-context-retrieval, no page yet). Definitions of symbols used near the cursor, recently edited chunks, open files and similar snippets found by lexical or vector search, ranked and cut to size. Code symbol indexing provides the "where is this function defined" lookup; Hybrid search rank fusion merges keyword and embedding hits. llama.vim shows a minimal version of the idea: a ring of context chunks from open files, edited regions and yanked text, plus reuse of the server's cached context so that large contexts stay affordable.
  • Metadata. File path, language, imports, and indentation of the current line.

The contract between this part and the model is a single string with sentinel tokens in the exact format the model was trained with. Continue's documentation is blunt about the consequence: strong general chat models that were not trained with the fill-in prompt format do not produce useful completions, and most state-of-the-art autocomplete models are no larger than about ten billion parameters.

Model and decoding

The model is a decoder-only Transformer trained on code with a large fraction of fill-in-the-middle examples; StarCoder and Code Llama are published open examples of code models with infilling. Decoding is deliberately different from chat:

  • Short and stoppable. Completions are a line or a block; generation stops at a newline, a closing bracket of matching depth, a token budget, or a wall-clock timeout. Generating less is the cheapest speed-up there is.
  • Low-randomness sampling. Greedy or a low temperature, since the user wants the most likely continuation, not a creative one.
  • Token healing (token-healing, no page yet). A prompt that ends in the middle of a token, such as after http:, puts the model in a distribution it rarely saw in training, because tokenizers merge characters greedily; the Guidance project's notebook on prompt boundaries shows a generated URL breaking for that reason. The fix is to back up one token and constrain the first generated token to start with the removed text. Code ends mid-identifier all the time, so this matters here more than in chat.
  • Optional constrained decoding to keep output inside a grammar, for instance balanced brackets, when a cheap structural guarantee is worth the mask computation.

Serving

An inline-completion fleet sees a high rate of small requests whose prompts mostly repeat the previous one. Cursor says its Tab model handles over 400 million requests per day, which is on the order of 4,600 a second on average (arithmetic from that figure). The serving layer exploits the repetition:

  • Prompt caching keeps the attention keys and values of a shared prompt prefix, so the next keystroke's request prefills only the new tokens. The context builder cooperates by placing stable material (file header, retrieved definitions) before volatile material (the lines at the cursor) and by not reshuffling snippets between requests.
  • Paged KV cache stores those caches in fixed blocks so thousands of live prefixes share GPU memory without fragmentation; the PagedAttention paper describes the idea and vLLM implements it.
  • Continuous batching adds and removes requests each decoding step instead of waiting for a batch, which keeps a GPU busy without making a short request wait for a long one.
  • Speculative decoding lets a cheap draft propose tokens that the main model verifies in one pass. Code edits are an ideal case, because much of an edit is copied from the existing file: Cursor describes a variant called speculative edits that reached about 1,000 tokens per second on a 70-billion-parameter model for whole-file rewrites.
  • Weight quantization and knowledge distillation shrink the model so that time to first token fits the budget; a distilled small model that was trained on the fill-in task usually beats a quantized large chat model on this workload.

The contract with the context builder is stable-prefix ordering; the contract with the client is a streamed response that stops when cancelled.

Post-processing, filter and cache

The raw completion is not shown yet. The post-processor trims trailing text that duplicates the suffix, cuts the completion at a sensible boundary (end of statement, end of block), re-indents it, and checks that the document still parses with the incremental parser. Then a show or hide decision: many systems train a small classifier on acceptance data so that a low-probability suggestion stays hidden, because a wrong suggestion costs attention. Cursor's write-up describes GitHub Copilot's published contextual filter as a logistic-regression score over a handful of features (language, whether the last suggestion was accepted, the characters before the cursor and others) with suggestions below a threshold skipped; Cursor's own approach replaces the separate filter with reinforcement learning on acceptance, with a reward that shows a suggestion only when its estimated accept chance exceeds a target (their example is 25 %). A small LRU/CLOCK cache keyed by the exact prompt returns an immediate answer when the user deletes a character and retypes it.

Measurement

The product metric is the accept rate and what survives afterwards. Ziegler et al. found that the rate at which shown suggestions are accepted, rather than how long completions persist in the code, was what best matched developers' perceived productivity in the Copilot study. That makes acceptance both the thing to optimise and a dangerous target, since showing fewer, safer suggestions raises it; pair it with the number of suggestions shown per hour and per-edit retention. Changes ship behind an A/B test with the guardrails listed there. Offline evaluation metrics on held-out fill-in tasks screen models before they reach users.

Data and control flow

One keystroke to one suggestion:

  1. The editor sends a change event; the incremental parser updates the syntax tree for the edited region.
  2. A debounce timer restarts. Any in-flight request is cancelled.
  3. After the delay, the context builder assembles prefix, suffix and snippets, ordered with stable parts first, within the token budget.
  4. The server finds the longest cached prefix, prefills only the remainder, and decodes with a stop rule and a timeout; the first tokens stream back.
  5. The post-processor trims, checks syntax and scores; the filter decides to show or hide.
  6. The client renders ghost text; Tab accepts it; any other key dismisses or narrows it. Where time goes: debounce delay, time to first token (dominated by prefill of the uncached part), and the few tens of decoded tokens. Cutting any one wins more than a faster GPU.

Acceptance feedback and the next model:

  1. Each shown suggestion is logged with its context hash, outcome (accepted, rejected, ignored) and latency.
  2. Offline, outcomes become labels for the filter or a reward for training. Cursor reports rolling out new models to users frequently throughout the day and training on that on-policy data, so the model that is learning is the one that is being judged.
  3. A candidate model is evaluated offline, then A/B tested on live traffic against the current model before being promoted.

Indexing the repository:

  1. A background job parses files with the incremental parser and extracts definitions and references.
  2. Only files whose content hash changed are re-indexed (Code symbol indexing); embeddings, if used, are stored in a vector index.
  3. At request time the context builder queries by the symbols near the cursor and gets ranked snippets back in a few milliseconds.

Scaling

In the order you would pull the levers:

  1. Send fewer requests. Debounce, cancel, skip positions where a suggestion is unlikely (inside a comment, after a closing line), serve repeats from cache. Everything saved here is saved at every layer below.
  2. Reuse prompt computation. Stable-prefix ordering and prompt caching; prefill of the new tokens, not the whole prompt.
  3. Shrink the model. A small, fill-in-trained or distilled model, quantized; accept lower raw capability for lower latency, since the context carries much of the quality.
  4. Raise GPU utilisation. Continuous batching, paged KV cache, speculative decoding for edits.
  5. Move computation closer. Regional inference near users, or a local model (llama.cpp, Ollama, Tabby) for privacy and zero network time, at the cost of a smaller model.
  6. Improve quality per request. Better retrieval, more fill-in training data, learning from acceptance; these raise accept rate without extra latency.

Failure modes

FailureSymptomCauseFix
Stale suggestionghost text for code that was already typed overresponse arrives after the cursor movedcheck document version and cursor on every response; trim or drop
Latency spikessuggestions lag typingcold prefix cache, queueing, long retrieved contextstable-prefix ordering, timeouts, cap the context, drop to a smaller model under load
Duplicate or missing bracketsextra ) or } after acceptingmodel ignored the suffix, or not fill-in-trainedinclude the suffix, trim overlap, use a model trained on the fill-in format
Broken at token boundarygarbled first charactersprompt ends mid-tokentoken healing
Chat model used as completerrambling or empty outputno fill-in traininguse a fill-in model; keep chat models for chat
Hallucinated APIcalls a function that does not existno repository context, stale indexsymbol-index retrieval, re-index changed files, check against the index
Low accept ratemany suggestions ignoredtoo many low-confidence suggestions shownlearned show/hide filter, reward that includes the cost of a wrong suggestion
Metric gamingaccept rate rises, productivity does notfewer, trivial suggestionstrack shown per hour and retention beside accept rate
Repetition loopsame line repeatedgreedy decoding without stop rule or penaltystop rules, repetition limit, mild penalty
Secret or private code in contextcredentials sent to a remote modelcontext builder includes ignored or sensitive filesignore lists, local mode, redaction before sending

Real implementations

namewhat it isrepolicencenotes
Continueopen-source assistant for VS Code and JetBrains with autocomplete, chat, editcontinuedev/continueApache-2.0documents the debounce, timeout, prompt budget and fill-in prompt format
Tabbyself-hosted code completion server, a Copilot alternativeTabbyML/tabbyApache-2.0 (the repo's ee/ directory has a separate licence)consumer-GPU friendly, OpenAPI interface
llama.vim with llama.cpplocal fill-in-the-middle completion for Vim and Neovim, backed by a local serverggml-org/llama.vim, ggml-org/llama.cppMITring-buffer context and context reuse in the server
tree-sitterincremental parser used for syntax checks and structuretree-sitter/tree-sitterMITparses on every keystroke
vLLMserving engine with paged KV cache and continuous batchingvllm-project/vllmApache-2.0a common backend for completion models
Guidanceprompt and decoding control library, token healing documentedguidance-ai/guidanceMITsee its prompt-boundaries notebook
StarCoder (BigCode)open code models with infilling and training codebigcode-project/starcoder2Apache-2.0StarCoder paper describes infilling; check model licences separately
GitHub Copilotthe commercial reference for inline completiongithub.com/features/copilot—closed source; the Ziegler et al. paper reports its acceptance-rate study
Cursor Tabnext-edit prediction trained online on acceptanceCursor blog—closed source; documented training loop

The alternative: a chat or agent model asked to "complete this code" gives better reasoning for large changes but is too slow and too chatty for keystroke-level completion. The two coexist: completion handles the next few tokens or lines in the editor, an agent handles tasks that span files.

Sources

  • M. Bavarian et al., Efficient Training of Language Models to Fill in the Middle, 2022. paper
  • A. Ziegler et al., Productivity Assessment of Neural Code Completion, MAPS 2022. paper
  • Language Server Protocol 3.18 specification (textDocument/inlineCompletion). spec
  • J. Jackson, P. Kravtsov, S. Jain, Improving Cursor Tab with online RL, Cursor, 2025. post
  • A. Sanger, Editing Files at 1000 Tokens per Second, Cursor, 2024. post
  • Continue documentation, Autocomplete. docs
  • Guidance, Prompt Boundaries and Token Healing. notebook
  • R. Li et al., StarCoder: may the source be with you!, 2023. paper
  • B. Rozière et al., Code Llama: Open Foundation Models for Code, 2023. paper
  • W. Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023. paper
  • Y. Leviathan, M. Kalman, Y. Matias, Fast Inference from Transformers via Speculative Decoding, 2022. paper

Similar systems

Share the most building blocks with this one.