{
    "slug": "typeahead_autocomplete",
    "term": "Typeahead & Autocomplete",
    "category": "search",
    "difficulty": "intermediate",
    "short": "Search suggestions shown as the user types — requiring prefix matching, typo tolerance, and sub-100ms response times to feel native, implemented via dedicated index structures or edge completion APIs.",
    "long": "Autocomplete has stricter performance requirements than regular search — suggestions must appear within 50–100ms of each keystroke or users stop typing and wait, destroying the experience. Implementation approaches vary by scale: for small datasets (<10,000 items), a PHP endpoint filtering in-memory or using LIKE 'prefix%' queries is adequate. For larger datasets, a dedicated prefix index (MySQL prefix search with an indexed column, PostgreSQL pg_trgm with a GIN index, or Elasticsearch completion suggester) is required. Typo tolerance adds significant complexity — Elasticsearch's completion suggester supports fuzziness; for simple PHP implementations, trigram similarity indexes (pg_trgm) handle one or two character errors. Debouncing requests on the frontend (wait 150–200ms after the last keystroke before querying) reduces server load and prevents out-of-order response races.",
    "aliases": [
        "autocomplete",
        "type-ahead",
        "search suggestions",
        "search as you type",
        "completion suggester"
    ],
    "tags": [
        "autocomplete",
        "search",
        "typeahead",
        "performance",
        "elasticsearch",
        "php"
    ],
    "misconception": "Autocomplete can be implemented with a simple LIKE '%partial%' query. LIKE with a leading wildcard ('%partial%') cannot use a B-tree index and performs a full table scan. Autocomplete requires prefix matching (LIKE 'partial%' — note no leading wildcard) or trigram indexes for substring matching. The leading wildcard disables the index entirely, making the query unacceptably slow on any meaningful dataset.",
    "why_it_matters": "Autocomplete is one of the most user-visible search features and one of the most commonly implemented incorrectly in PHP applications. A typeahead that takes 500ms to respond feels broken — users perceive anything over 200ms as a lag. The implementation mistake of using LIKE '%partial%' works fine in development with 100 rows and fails silently in production with 100,000. Building autocomplete correctly from the start means: prefix-only matching, a proper index, debounced requests, and a response cache for common prefixes.",
    "common_mistakes": [
        "Using LIKE '%term%' instead of LIKE 'term%' — the leading wildcard disables all indexes.",
        "Not debouncing keystrokes — firing a request on every keypress creates a thundering herd of requests and race conditions when responses arrive out of order.",
        "Not caching popular prefix responses — the same two or three character prefixes generate the majority of autocomplete queries; cache them aggressively.",
        "Returning too many suggestions — 5–8 suggestions is the usable maximum; returning 20 forces scrolling and degrades the experience."
    ],
    "when_to_use": [],
    "avoid_when": [],
    "related": [
        "inverted_index",
        "full_text_search",
        "elasticsearch_fundamentals",
        "search_relevance"
    ],
    "prerequisites": [],
    "refs": [
        "https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html"
    ],
    "bad_code": "// Full table scan — unusable at scale, SQL injection risk\n$results = $pdo->query(\n    \"SELECT name FROM products WHERE name LIKE '%{$q}%' LIMIT 10\"\n)->fetchAll();\n// No index used, leading wildcard kills performance",
    "good_code": "// Prefix match — uses index, safe\n$stmt = $pdo->prepare(\n    'SELECT id, name FROM products\n     WHERE name LIKE :prefix\n     ORDER BY popularity DESC\n     LIMIT 8'\n);\n$stmt->execute([':prefix' => $q . '%']); // note: prefix%, not %prefix%\n$results = $stmt->fetchAll();\n\n// With Redis cache for hot prefixes\n$cacheKey = 'autocomplete:' . strtolower(substr($q, 0, 3));\n$cached = $redis->get($cacheKey);\nif ($cached) return json_decode($cached);\n// ... query and cache with 60s TTL",
    "quick_fix": "Debounce frontend requests by 150ms, use LIKE 'term%' (not '%term%') with an indexed column, cache top-N results per prefix in Redis with a short TTL",
    "severity": "medium",
    "effort": "medium",
    "created": "2026-03-23",
    "updated": "2026-04-05",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/typeahead_autocomplete",
        "html_url": "https://codeclaritylab.com/glossary/typeahead_autocomplete",
        "json_url": "https://codeclaritylab.com/glossary/typeahead_autocomplete.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Typeahead & Autocomplete](https://codeclaritylab.com/glossary/typeahead_autocomplete) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/typeahead_autocomplete"
            }
        }
    }
}