The 60 Elasticsearch questions interviewers actually ask, with direct answers, runnable queries, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
60 questions with answersKey Takeaways
Elasticsearch is an open-source, distributed search and analytics engine built on top of Apache Lucene, first released in 2010. It stores data as JSON documents inside indices, builds an inverted index over the text, and returns matches in near real time. It's the heart of the ELK Stack (Elasticsearch, Logstash, Kibana) and handles full-text search, log analytics, metrics, and vector search from one system. Because it's distributed, data splits into shards spread across nodes, and replicas keep copies for fault tolerance and read throughput. In interviews, questions probe how the inverted index works, how mapping and analyzers shape what's searchable, how the query DSL expresses intent, and how the cluster stays consistent and available under load. This page collects the 60 questions that come up most, each with a direct answer and a runnable query or API call. For fundamentals, the official get started guide from Elastic is the canonical path; increasingly the first technical round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.
Watch: SQL Tutorial - Full Database Course
Video: SQL Tutorial - Full Database Course (freeCodeCamp.org, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Elasticsearch certificate.
The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.
Elasticsearch is a distributed, open-source search and analytics engine built on Apache Lucene. It stores data as JSON documents inside indices, builds an inverted index over the text, and returns matches in near real time, which is what makes it feel instant even over huge datasets that a single database would struggle to search.
Teams use it for full-text search, log and metric analytics (the ELK Stack), autocomplete, and increasingly vector search. It scales horizontally by spreading data across nodes, so it handles large volumes that a single database struggles with.
Key point: A one-line definition plus two concrete use cases (search and log analytics) beats a memorized feature list. Interviewers open with this to hear how you organize an answer.
Watch a deeper explanation
Video: What is Elasticsearch? (IBM Technology, YouTube)
An inverted index maps every unique term to the list of documents that contain it, plus the positions where it appears. Searching for a word looks that word up once and gets back the matching document list, instead of scanning every document field by field the way a database LIKE query does.
It's the same idea as the index at the back of a book. That lookup is what lets Elasticsearch search millions of documents in milliseconds where a database LIKE scan would take seconds.
Key point: Draw the term-to-documents mapping if you have a whiteboard. Showing the structure, not just naming it, is what the question needs here.
Watch a deeper explanation
Video: Inverted Indexing Explained: Elastic Search and Google Crawlers (Kartikeya Sharma, YouTube)
A document is a single JSON object, the basic unit you store and search. A field is one key-value pair inside a document. An index is a collection of documents with similar structure, roughly like a table's role in a database.
Each document has an _id and lives in exactly one index. Fields have types defined by the index's mapping, which controls how each field is stored and searched.
PUT /products/_doc/1
{
"name": "Wireless mouse",
"price": 29.99,
"in_stock": true
}Key point: Interviewers use the database analogy (index is like a table, document is like a row) as a springboard, then ask where it breaks down. Be ready for that follow-up.
A node is a single running instance of Elasticsearch, usually one per machine or container. A cluster is one or more nodes that share the same cluster name and work together, holding all the data between them and handling indexing and search requests as one coordinated group rather than as isolated servers.
Nodes take roles: master-eligible nodes manage cluster state, data nodes hold shards, and coordinating nodes route requests. Small setups run all roles on every node; larger ones separate them for stability.
Elasticsearch is built for full-text search, relevance ranking, and analytics over semi-structured JSON, and it scales out across nodes by design. A relational database is built for transactional writes, joins, and strong ACID guarantees over structured tables, which are exactly the properties Elasticsearch trades away to get its search speed and scale.
Elasticsearch is eventually consistent and near real time, has no true multi-document transactions, and denormalizes data instead of joining. Teams often keep the source of truth in a SQL database and index a searchable copy in Elasticsearch.
| Concept | Relational database | Elasticsearch |
|---|---|---|
| Grouping | Table | Index |
| Row | Row | Document (JSON) |
| Column | Column | Field |
| Schema | Strict, defined upfront | Mapping, dynamic or explicit |
| Best at | Transactions and joins | Search and analytics |
Key point: Saying 'it's not a system of record' in practice signals real-world experience. the key point is you wouldn't make it your only datastore.
A text field is analyzed: it's broken into tokens for full-text search, so a search for one word matches a document containing it in a sentence. A keyword field is stored as a single un-analyzed token, kept exactly as given.
Use text for anything you search by words (descriptions, titles) and keyword for exact-match, sorting, and aggregations (status codes, tags, IDs). A field can be indexed as both via a multi-field mapping.
PUT /articles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": { "raw": { "type": "keyword" } }
}
}
}
}| text | keyword | |
|---|---|---|
| Analyzed | Yes (tokenized) | No (exact) |
| Full-text search | Yes | No |
| Sort and aggregate | No (by default) | Yes |
| Typical use | Descriptions, bodies | Tags, IDs, status |
Key point: This maps directly to the most common mapping mistake. If you can explain the multi-field pattern, you've answered the follow-up before it's asked.
A match query analyzes the search text the same way the field was analyzed at index time, then looks for the resulting tokens, so it's the standard full-text query. A term query looks for the exact token you give it, with no analysis at all, which is why it belongs on keyword fields rather than analyzed text.
That's why a term query for "Hello" against an analyzed text field usually returns nothing: the indexed token was lowercased to "hello". Use match on text fields, term on keyword fields.
GET /articles/_search
{
"query": {
"match": { "title": "distributed search" }
}
}
GET /logs/_search
{
"query": {
"term": { "status": "error" }
}
}Key point: The gotcha (term on a lowercased text field returns nothing) is the exact thing this question screens for. Volunteer it.
An analyzer turns raw text into the tokens that go into the inverted index. It runs in three stages: character filters clean the raw text first, a tokenizer splits it into individual tokens, and token filters then transform those tokens by lowercasing, removing stop words, or stemming. The tokens that come out are exactly what a search has to match.
The default standard analyzer splits on word boundaries and lowercases. The same analyzer runs at index time and search time, which is why match works and term often doesn't.
POST /_analyze
{
"analyzer": "standard",
"text": "The Quick Brown Foxes!"
}
# tokens: the, quick, brown, foxesKey point: Naming the three stages (char filters, tokenizer, token filters) in order is the depth marker for this question.
A mapping defines the fields in an index and their types, plus how each field is indexed and analyzed. It's the schema layer that controls what you can search, sort, and aggregate, and getting it right upfront saves painful reindexing later because a field's type can't be changed once data is in.
You can let Elasticsearch infer types with dynamic mapping, or define an explicit mapping upfront. You can't change the type of an existing field; you create a new index with the corrected mapping and reindex.
GET /products/_mappingKey point: The line the key signal is: you cannot change an existing field's type in place. Knowing you reindex instead is the signal.
Index with PUT or POST to /index/_doc, read with GET by _id, update with POST to /_update, and delete with DELETE by _id. Elasticsearch exposes all of this over a REST API with JSON request bodies, so the same four operations you know from any datastore map onto plain HTTP verbs against a document endpoint.
Updates are not in-place: Lucene segments are immutable, so an update marks the old document deleted and writes a new version. The document's _version field increments on every change.
PUT /products/_doc/1
{ "name": "Keyboard", "price": 49 }
GET /products/_doc/1
POST /products/_update/1
{ "doc": { "price": 45 } }
DELETE /products/_doc/1The Query DSL is Elasticsearch's JSON-based language for defining searches. You send a query object in the request body, and it describes both what to match and how to evaluate the matches, so the shape of that JSON is the whole search. Everything from a simple keyword lookup to a complex ranked search is expressed this way.
It splits into two families: leaf queries like match and term that target a field, and compound queries like bool that combine leaf queries. Everything you write in a search request is Query DSL.
GET /products/_search
{
"query": {
"range": { "price": { "gte": 20, "lte": 50 } }
}
}A bool query combines other queries with four clauses: must (must match and contributes to the score), should (matches boost the score, optional unless minimum_should_match is set), filter (must match but no scoring, and results are cached), and must_not (excludes any match, no scoring). It's the workhorse compound query behind most real searches.
It's the workhorse for real searches: require some conditions, prefer others, and filter on exact fields all in one query.
GET /products/_search
{
"query": {
"bool": {
"must": [{ "match": { "name": "laptop" } }],
"filter": [{ "range": { "price": { "lte": 1000 } } }],
"must_not": [{ "term": { "refurbished": true } }]
}
}
}Key point: Interviewers love asking which clause is evaluated and which is cached. must and should score; filter and must_not don't.
In query context a clause answers 'how well does this document match?' and contributes to the relevance _score. In filter context it answers 'does this document match, yes or no?' with no scoring at all. The difference matters for both correctness and speed, because filter results skip scoring and get cached for reuse.
Filter context is faster and cacheable, so use it for exact conditions (a date range, a status, a boolean) and reserve query context for the relevance-driven parts of the search. Clauses inside filter and must_not run in filter context automatically.
Key point: The practical takeaway to state: move anything exact into a filter for speed and cache reuse. That's the performance instinct they're checking for.
A GET by _id is a direct real-time lookup: it fetches one known document and sees the latest version immediately, even before a refresh. A search runs a query across the inverted index and only sees documents that a refresh has made searchable.
So GET is real time, search is near real time. That gap explains the classic 'I just indexed a document and search can't find it yet' surprise.
Key point: If you can The refresh interval as the reason search lags behind GET, you've shown you understand near real time, not just memorized it.
ELK stands for Elasticsearch, Logstash, and Kibana. Logstash (or the lighter Beats agents) collects and transforms data from many sources, Elasticsearch stores and searches it, and Kibana visualizes it with dashboards and lets you explore it. Together they're the common pattern for centralized logging and observability across a fleet of servers.
It's the common pattern for centralized logging and observability: ship logs from many servers, index them, and explore them in one place. Elastic now calls the broader family the Elastic Stack.
Through a RESTful HTTP API that takes and returns JSON. Every operation, whether indexing, searching, changing a mapping, or managing the cluster, is an HTTP request to an endpoint, which is why you can drive it from curl, from Kibana's Dev Tools console, or from any official language client without learning a separate query protocol.
Official clients exist for Java, Python, JavaScript, and more; they wrap the same REST calls. This uniform API is part of why Elasticsearch is easy to integrate.
You can supply your own _id with PUT /index/_doc/your-id, which is the right choice when the document maps to a known key like a user ID or an order number. If you POST without an _id, Elasticsearch generates a random URL-safe one for you, which suits data like log lines that have no natural key.
Auto-generated IDs are slightly faster to index because Elasticsearch skips the check for whether the ID already exists. Custom IDs make upserts and idempotent indexing possible.
size sets how many hits to return and from sets the offset into the sorted results, so from: 20 with size: 10 returns results 21 to 30. It's the simple, obvious way to page through a result set, and it works fine for the first few pages where most users actually go.
It doesn't scale deep, though: each shard has to collect from + size hits and the coordinating node sorts them all, so deep pages get expensive. Past a few thousand results, use search_after or the point in time API instead.
GET /products/_search
{
"from": 20,
"size": 10,
"query": { "match_all": {} }
}Key point: The follow-up is always 'why is deep pagination slow?'. The from + size cost per shard is the answer they want.
match_all matches every document in the index and gives each a constant score of 1. It's how you retrieve or sample everything, and it's the default query Elasticsearch uses when you send a search with no query body at all, so an empty search and a match_all search behave the same way.
To count without pulling documents, use the _count API or set size: 0 on a search; total hits come back without transferring the documents themselves.
GET /products/_count
{ "query": { "match_all": {} } }Kibana is the visualization and management UI that sits in front of Elasticsearch. You build dashboards, charts, and maps over your indexed data, and its Dev Tools console lets you run raw queries against the cluster without writing any client code, which makes it the fastest way to explore data and test queries by hand.
It's also where a lot of operational work happens: index lifecycle management, watching cluster health, and defining data views. For interviews, know it as the K in ELK and the fastest way to explore data.
The _cat APIs return cluster information as compact, human-readable tables instead of JSON, so you can eyeball the state of indices, shards, nodes, and health straight from a terminal. They're built for people, not programs, which makes them the quick way to inspect a cluster during operations or debugging.
Common ones are _cat/indices to list indices with their sizes and doc counts, _cat/shards to see where each shard lives, _cat/nodes for the node roster, and _cat/health for a one-line status. Add ?v for a header row and ?s to sort.
GET /_cat/indices?v&s=store.size:desc
GET /_cat/shards?v
GET /_cat/nodes?vKey point: Reaching for _cat/shards to find an unassigned shard, rather than parsing JSON, signals you've actually operated a cluster, not just queried one.
For candidates with working experience: query mechanics, mapping judgment, and the internals that separate users from understanders.
Relevance scoring ranks matching documents by how well they fit the query, producing the _score you see in each hit and driving the default sort order. Since version 5, the algorithm behind that score is Okapi BM25, which replaced the older TF-IDF scoring as the built-in default across text search.
BM25 rewards documents where the term appears often (term frequency), down-weights terms that appear in many documents (inverse document frequency), and normalizes for field length so a match in a short title outweighs the same match buried in a long body. It improves on classic TF-IDF by saturating term frequency, so the tenth occurrence adds far less than the second.
Key point: Say TF-IDF and then that BM25 improved on it with saturation and length normalization. Naming both, and the difference, is the production signal.
Watch a deeper explanation
Video: Beginner's Crash Course to Elastic Stack Part 2: Relevance of a search (Official Elastic Community, YouTube)
You configure it in the index settings by combining a tokenizer with character filters and token filters, then reference it in the mapping. This lets you handle things the standard analyzer won't, like a custom stop-word list, synonyms, or edge n-grams for autocomplete.
The rule to respect: the analyzer used at index time and search time should match, or your queries won't line up with the indexed tokens. You can override the search analyzer deliberately, but do it knowingly.
PUT /blog
{
"settings": {
"analysis": {
"analyzer": {
"my_english": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "english_stop", "english_stemmer"]
}
},
"filter": {
"english_stop": { "type": "stop", "stopwords": "_english_" },
"english_stemmer": { "type": "stemmer", "language": "english" }
}
}
}
}The Bulk API sends many index, update, or delete operations in a single HTTP request, which cuts network round trips and per-request overhead, so it's the right way to ingest data at volume. Indexing documents one at a time is far slower because each write pays the full request cost on its own.
The body uses newline-delimited JSON: an action line, then the document line, repeated. Each item succeeds or fails on its own, so you check the response for per-item errors rather than assuming all-or-nothing.
POST /_bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "name": "Mouse", "price": 25 }
{ "index": { "_index": "products", "_id": "2" } }
{ "name": "Cable", "price": 8 }Key point: The detail that indicates experience: bulk is partial-success, so you must inspect the errors flag and per-item statuses, not just the HTTP code.
Watch a deeper explanation
Video: Elasticsearch Course for Beginners (freeCodeCamp.org, YouTube)
Aggregations summarize data instead of returning documents, giving you counts, averages, groupings, and histograms computed across the matching set. They're what power dashboards and analytics over indexed data, so a single request can return both the matching documents and rolled-up numbers about them at the same time.
The families are bucket aggregations that group documents (terms, date_histogram, range), metric aggregations that compute numbers over a set (avg, sum, min, max, cardinality), and pipeline aggregations that operate on the output of other aggregations. Nesting a metric inside a bucket is the common pattern, like average price per category.
GET /sales/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category" },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}Key point: Setting size: 0 so you get aggregation results without the hits is the small touch that shows you've done this for real.
Watch a deeper explanation
Video: Beginner's Crash Course to Elastic Stack Part 3: Full text queries (Official Elastic Community, YouTube)
By default Elasticsearch flattens an array of objects, losing the association between fields inside each object. A query for name: Alice and role: admin can match a document where Alice is a viewer and someone else is the admin, because the values got mixed.
The nested type indexes each sub-object as a hidden separate document, preserving the field associations, so nested queries match within one object. The cost is more storage and slightly slower queries, so use nested only when cross-field matching inside array objects matters.
PUT /teams
{
"mappings": {
"properties": {
"members": { "type": "nested" }
}
}
}Key point: The flattening gotcha is the whole point of this question. Describe the Alice-the-admin false match and you've nailed it.
You can't change an existing field's type, so you create a new index with the corrected mapping and copy the data with the Reindex API. Aliases make the switch invisible to clients: point the alias at the new index once reindexing finishes.
For zero-downtime changes, index writes go to the new index while reindex copies the old, then flip the alias. This alias pattern is the standard answer to any 'how do you evolve a schema' question.
POST /_reindex
{
"source": { "index": "products_v1" },
"dest": { "index": "products_v2" }
}Key point: aliases for a zero-downtime cutover is what matters.
An alias is a named pointer to one or more indices that you query and write through instead of using the real index name. Clients only ever talk to the alias, so you can swap the underlying index, split traffic across several, or roll to a new version without changing a single line of application code.
Aliases enable zero-downtime reindexing, rolling time-based indices behind one search name, and filtered views that limit what a caller sees. They cost nothing and remove a whole class of coupling.
POST /_aliases
{
"actions": [
{ "remove": { "index": "products_v1", "alias": "products" } },
{ "add": { "index": "products_v2", "alias": "products" } }
]
}New documents go into an in-memory buffer and aren't searchable until a refresh flushes them into a searchable Lucene segment. Refresh runs about once per second by default, so there's a short lag between indexing and being findable by search.
A direct GET by _id sees the document immediately because it can read the translog, but search waits for the refresh. You can force a refresh for tests, but doing it on every write hurts throughput badly.
Key point: The trap is forcing refresh in production to 'fix' the lag. Naming that as a throughput killer is the right instinct to show.
The translog is a write-ahead log. Every index and delete is appended to it before the change is acknowledged, so if a node crashes before the in-memory buffer is flushed to disk, Elasticsearch replays the translog on restart and no acknowledged write is lost.
Refresh makes documents searchable; flush persists segments to disk and clears the translog. The two are separate operations, which is a distinction interviewers probe.
The range query works on numbers, dates, and IP addresses with gte, gt, lte, and lt bounds. For dates, Elasticsearch supports date math like now-7d/d, which means seven days ago rounded down to the start of the day, so you can express relative time windows without computing exact timestamps in the client.
Range queries usually belong in filter context because they're exact yes/no conditions, so they're cached and don't affect scoring. That's a small optimization interviewers like to see.
GET /logs/_search
{
"query": {
"bool": {
"filter": {
"range": { "@timestamp": { "gte": "now-7d/d", "lte": "now" } }
}
}
}
}The multi_match query runs a match against several fields and combines the results. Its type controls how: best_fields takes the single best-scoring field (good for titles versus bodies), most_fields blends all matches, and cross_fields treats the fields as one big field for terms spread across them.
You can boost individual fields with the caret syntax, like title^3, to weight a title match above a body match.
GET /articles/_search
{
"query": {
"multi_match": {
"query": "distributed search",
"fields": ["title^3", "body"],
"type": "best_fields"
}
}
}For typo tolerance, use the fuzzy query or set fuzziness on a match, which matches terms within a Levenshtein edit distance of the query term. fuzziness: AUTO scales the allowed edits by term length, so short words tolerate fewer typos than long ones, which is the sensible default rather than a fixed edit count.
For partial matching, wildcard and regexp queries exist but scan many terms and can be slow, so for prefix autocomplete prefer edge n-grams at index time or the search_as_you_type field type. Reaching for the index-time solution over runtime wildcards is the mature choice.
GET /products/_search
{
"query": {
"match": {
"name": { "query": "keybord", "fuzziness": "AUTO" }
}
}
}Key point: Interviewers like hearing you avoid leading-wildcard queries and solve autocomplete with n-grams at index time instead.
Add a highlight block to the search naming the fields you want highlighted, and Elasticsearch returns snippets with the matching terms wrapped in tags, which default to the <em> tag. It's what powers the bolded query terms you see in search result pages, drawing the user's eye to why each result matched.
You control the tags, snippet size, and number of fragments. Highlighting re-analyzes field content, so it adds cost; request it only on the fields you actually display.
GET /articles/_search
{
"query": { "match": { "body": "search engine" } },
"highlight": {
"fields": { "body": {} }
}
}Use _source filtering to include or exclude specific fields, which cuts response size and network cost when documents are large. Set _source to a list of fields you want, to an includes and excludes object for finer control, or to false to return only the metadata and skip the stored document body entirely.
For high-throughput reads, storing fields as doc_values and using the fields parameter or excluding heavy fields from _source keeps payloads small. Returning only what the client needs is a cheap, real win.
GET /products/_search
{
"_source": ["name", "price"],
"query": { "match_all": {} }
}Dynamic mapping infers a field's type from the first value it sees, so you can index JSON without defining a schema. It's convenient for prototyping but risky in production: a stray string in a numeric field, or unexpected keys, can create the wrong type or a field explosion.
For controlled indices, define explicit mappings and set dynamic to strict (reject unknown fields) or false (ignore them). This prevents mapping surprises and the mapping-explosion problem where thousands of dynamic fields bloat the cluster state.
Key point: Naming the mapping-explosion risk from unbounded dynamic keys is the detail that separates prototype thinking from production thinking.
Green means all primary and replica shards are assigned. Yellow means all primaries are assigned but at least one replica isn't, so data is fully available but not fully redundant, common on a single-node cluster. Red means at least one primary shard is unassigned, so some data is missing and searches over it are incomplete.
Check it with the _cluster/health API. Yellow is often fine in development; red needs immediate attention.
GET /_cluster/healthKey point: Knowing that a single-node cluster is yellow by design (replicas can't be assigned) heads off a false alarm and shows you understand the cause.
Doc values are a column-oriented, on-disk structure that stores each field's values grouped by document, which is the inverse of how the inverted index groups documents by term. They're what make sorting, aggregations, and script field access efficient, because those operations need the values for a given document, not the documents for a given term.
They're on by default for most field types and stored on disk (memory-mapped), so they scale beyond RAM. Analyzed text fields don't have doc values, which is why you can't aggregate or sort on a text field without a keyword sub-field.
Key point: Connecting 'text fields lack doc values' to 'that's why you add a keyword sub-field to sort or aggregate' ties two questions together and indicates real understanding.
The Update By Query and Delete By Query APIs apply a change to every document a query matches, running the operation in batches across shards. Update By Query can also just reindex documents in place to pick up a mapping or analyzer change.
Both create a snapshot at the start and version-check each document, so concurrent changes are handled with a conflict count you can inspect. They run asynchronously for big jobs via a task you can monitor.
POST /products/_update_by_query
{
"query": { "term": { "discontinued": true } },
"script": { "source": "ctx._source.price = 0" }
}The node that receives the request acts as the coordinating node. It runs the query in two phases: query then fetch. In the query phase it forwards the search to a copy of every relevant shard, and each shard returns the IDs and scores of its top hits.
The coordinating node merges and sorts those, then in the fetch phase asks the relevant shards for the full documents of the final top hits. Understanding this two-phase flow explains why deep pagination is costly: every shard must produce from + size candidates.
Key point: Walking the query-then-fetch phases, and tying it back to deep-pagination cost, is exactly the internals depth this question is testing.
An index template defines settings, mappings, and aliases that apply automatically to any new index whose name matches a given pattern. They're what keep time-based indices consistent, so every daily index like logs-2026-07-04 picks up the same mapping and shard settings the moment it's created, with no manual setup for each one.
Modern Elasticsearch uses composable index templates plus reusable component templates, so you share common pieces (like a standard mapping) across many templates. Pair them with data streams and ILM for hands-off log management.
advanced rounds probe internals, distributed behavior, and production scars. Expect every answer here to draw a follow-up.
Each shard is a Lucene index made up of immutable segments. New documents create small new segments, a delete only marks the document in a per-segment file rather than removing it in place, and an update is really a delete of the old document plus an index of a new one. Segments never change once written.
Because segments are immutable, Lucene periodically merges small segments into larger ones in the background, which is when deleted documents are finally purged. This model explains why disk usage can exceed live data before a merge, why refresh is cheap, and why heavy update workloads generate merge pressure.
Key point: Connecting immutable segments to deleted-doc bloat and merge pressure is the internals story senior interviewers are listening for.
Watch a deeper explanation
Video: Beginner's Crash Course to Elastic Stack Part 1: Intro to Elasticsearch and Kibana (Official Elastic Community, YouTube)
Elasticsearch is eventually consistent for search and does not offer multi-document ACID transactions the way a relational database does. Single-document operations are atomic and use optimistic concurrency control through sequence numbers and primary terms, so you can safely do compare-and-set writes on one document even when several clients race to update it.
A write is acknowledged once the primary and the required number of in-sync replicas have it, controlled by wait_for_active_shards. There's no cross-document isolation, so patterns that need transactions belong in a relational database, with Elasticsearch as the search layer.
Key point: Naming optimistic concurrency (if_seq_no and if_primary_term) for safe single-document updates shows you've handled concurrent writes for real.
Split brain is when a network partition lets two parts of a cluster each elect their own master node, and the two halves then diverge with conflicting cluster state. Older Elasticsearch relied on the manual minimum_master_nodes quorum setting, which admins could misconfigure into exactly this failure if they got the number wrong.
Since version 7 the cluster coordination layer manages the voting configuration itself: masters are elected by a majority of master-eligible nodes, and the quorum is maintained automatically. The operator guidance is to run an odd number of dedicated master-eligible nodes (three is common) so a majority is always well defined.
Key point: three dedicated master-eligible nodes and automatic voting config (post-7) matters.
Elasticsearch stores embeddings in dense_vector fields and runs approximate nearest-neighbor search over them using an HNSW graph index, exposed through the knn search option. This finds documents whose vectors are closest to a query vector, which is the basis of semantic search and retrieval for AI applications.
The common production pattern is hybrid search: combine BM25 keyword scoring with kNN vector similarity, often blended with reciprocal rank fusion, so you get exact-term precision and semantic recall together. Knowing that keyword and vector search complement rather than replace each other is the current, informed take.
GET /docs/_search
{
"knn": {
"field": "embedding",
"query_vector": [0.12, 0.88, 0.05],
"k": 10,
"num_candidates": 100
}
}Key point: Framing hybrid search (BM25 plus kNN) as complementary, not a replacement, is the answer that indicates current with where search is going.
For time-series data like logs, a hot-warm-cold architecture puts recent, heavily queried data on fast hardware (hot), moves aging data to cheaper nodes (warm), then to slow, dense storage (cold), and eventually deletes it. It matches cost to how the data is actually accessed.
Index Lifecycle Management automates the transitions: a policy rolls over the write index by age or size, then moves, shrinks, force-merges, freezes, and deletes indices on a schedule. Data streams plus ILM are the standard hands-off setup for logging at scale.
The Index Lifecycle Management phases
ILM drives these phase changes on a policy so no one manages indices by hand.
Key point: Tying each ILM phase to an access pattern (and naming rollover as the trigger) is what turns this from a buzzword into an operational answer.
from + size gets expensive at depth because every shard must gather from + size hits and the coordinator sorts them all. search_after avoids that: you sort by a stable key including a tiebreaker, and each page passes the sort values of the last hit to fetch the next page, so cost stays flat.
For a consistent view across pages while data changes, open a point in time and pass its ID with search_after; the PIT pins the segments so results don't shift mid-scroll. The older scroll API still exists but is discouraged for user-facing pagination.
GET /products/_search
{
"size": 20,
"sort": [ { "price": "asc" }, { "_id": "asc" } ],
"search_after": [49.99, "product-8421"]
}Key point: Preferring search_after with a point in time over scroll for live pagination is the current best-practice answer the question needs.
Since there are no relational joins, you pick a modeling strategy per case: denormalize by copying related data into each document (fast reads, but duplicated data to keep in sync), use nested objects for arrays where field associations must hold, or use a parent-child join field when children update far more often than parents.
Denormalization is the default because it matches how search works and keeps queries fast. Nested and join both cost query performance, so reach for them only when their specific need (in-object matching, independent child updates) actually applies.
| Strategy | Best when | Cost |
|---|---|---|
| Denormalize (copy data) | Read-heavy, relations rarely change | Duplicate data, update fan-out |
| Nested type | Arrays of objects need field associations | More storage, slower queries |
| Parent-child join | Children update independently and often | Slower queries, same-shard constraint |
Key point: Leading with 'denormalize by default, and here's when nested or join earns its cost' is the judgment senior interviewers are grading.
Circuit breakers are guardrails that stop a single operation from using so much memory it crashes the node with an OutOfMemoryError. Elasticsearch tracks estimated memory for requests, field data, and aggregations, and rejects an operation with a 429 when it would push usage past a limit.
So a runaway aggregation or a huge request fails loudly instead of taking the node down. Seeing circuit-breaker exceptions is a signal to fix the query or the mapping (often a text field aggregated by accident), not just to raise the limit.
Key point: Treating a circuit-breaker trip as a symptom to diagnose, not a limit to bump, is the operations maturity this question checks.
the Profile API to see where time goes per shard and per query component, and check the slow log to catch the offending queries in production comes first. Common culprits: leading wildcards, aggregations on high-cardinality fields, deep pagination, unbounded scripts, and too many small shards forcing excess coordination.
Then fix at the right layer: move exact conditions into filter context for caching, replace runtime wildcards with index-time n-grams, cap or approximate high-cardinality aggregations, right-size shards, and add doc_values or a keyword sub-field where sorting or aggregating needs it. Measure again to confirm.
Key point: Observe, profile, hypothesize, fix, re-measure. The methodology matters more than any single trick.
Mapping explosion is when an index accumulates thousands of fields, usually because dynamic mapping keeps inventing fields from unbounded keys (like using user-supplied values as field names). The mapping becomes part of the cluster state, so a huge one slows the whole cluster and eats heap.
Prevent it by setting index.mapping.total_fields.limit, using dynamic: strict or false, and modeling variable keys as an array of key-value pairs (the flattened type or a nested list) instead of dynamic fields. It's a design problem, not a tuning knob.
Key point: Recognizing user-supplied keys as field names is the anti-pattern behind most real mapping explosions. Naming that root cause is the tell.
Cross-cluster search lets one cluster query indices that live on remote clusters in a single request, so you can search across regions or environments without physically moving the data. Cross-cluster replication is different: it continuously copies indices from a leader cluster to one or more follower clusters, keeping a live second copy elsewhere.
Together they support disaster recovery (a follower in another region ready to take over), keeping data near users for low-latency reads, and centralizing search over federated clusters. Knowing which solves which (search federates, replication copies) is the distinction to draw.
Force-merge combines a shard's segments into fewer, larger ones, which speeds up search and finally reclaims the space held by documents that were only marked deleted. It's worth doing on read-only indices, like older time-based indices that no longer receive any writes, where you typically merge down toward a single segment per shard.
The risk is running it on an index that still gets writes: you create one giant segment that then can't be merged normally and holds onto deleted-document space, hurting performance. So force-merge only when writes have stopped, usually as an ILM warm-phase step.
Key point: The pass/fail line is 'never force-merge an actively written index'. Saying that in practice is the safeguard the key point is.
A terms aggregation runs per shard and each shard returns only its top buckets, then the coordinator merges them. A term that's not in a shard's top list can be undercounted or missed, so the doc counts can be approximate, which the response flags with doc_count_error_upper_bound.
You reduce the error by raising the shard_size (how many buckets each shard returns before merging) at the cost of memory, or for exact distinct counts use composite aggregations to paginate through all buckets. cardinality itself is an approximate distinct count via HyperLogLog, tunable with precision_threshold.
Key point: Explaining the per-shard top-N mechanic behind the approximation, and shard_size as the lever, is the internals detail this question is really after.
The Snapshot and Restore API takes incremental snapshots of indices to a registered repository such as S3 or a shared filesystem. Each snapshot only stores the segments that aren't already saved from a previous one, so after the first full snapshot every later snapshot is cheap in both time and storage, which makes frequent backups practical.
You restore whole indices or selected ones, and searchable snapshots let cold data live in the repository while still being queryable, which cuts storage cost. Snapshots are the supported backup path; copying the data directory of a running node is not safe.
Key point: Warning that you never back up by copying a live data directory, only via the snapshot API, shows you know the one way this goes wrong in production.
The coordinating node routes the document to its primary shard by hashing the routing value (the _id by default) modulo the number of primary shards. The primary indexes it into its in-memory buffer and appends to the translog, then forwards the operation to its replica shards in parallel.
Once the primary and the required in-sync replicas have applied it, the write is acknowledged. A refresh later moves the buffer into a searchable segment, and a flush eventually persists segments and trims the translog. The routing hash is why you can't add primary shards later without reindexing: it would scatter existing documents.
Key point: Connecting the routing-hash formula to 'why primary shard count is immutable' ties the write path to a fact from the freshers tier and indicates deep understanding.
First measure with real judgments: gather queries where users didn't find what they wanted, and use the Rank Evaluation API or click data to quantify quality rather than guessing. Then work the levers in order: fix analysis (synonyms, stemming, language analyzer), weight fields with boosts, and tune the query shape (multi_match type, phrase matching, minimum_should_match).
Beyond that, use function_score or a rescore window to fold in business signals like popularity or recency, and for the hardest cases add semantic vector search in a hybrid setup or learning-to-rank. The discipline is measure, change one thing, re-measure, not blind boosting is the technical point.
Key point: Insisting on measuring relevance (judgments or click data) before tuning is the process signal. Blind boost-tweaking is the anti-answer here.
Clarify the numbers first (ingest rate, retention, query patterns), then design around time: use data streams with rolling indices so each day or size threshold starts a fresh index, which keeps shards a sane size and makes deletion a cheap index drop instead of a delete-by-query. Right-size primary shards toward the 10 to 50 GB range and add data nodes to spread the write load.
Tune ingestion for throughput: batch with the Bulk API, raise the refresh interval on write-heavy indices so segments form less often, and consider setting replicas to zero during the initial bulk load, then adding them after. Layer a hot-warm-cold tier with ILM so recent data sits on fast disks and old data ages to cheap storage or searchable snapshots.
Key point: Naming the raise-refresh-interval and defer-replicas tricks for bulk loads, plus index-drop deletion via rollover, is what marks this as a lived-through design rather than a textbook one.
Elasticsearch wins when you need fast full-text search, ranking by relevance, and analytics over large volumes of semi-structured data. It trades strong transactional guarantees for horizontal scale and search speed, so it isn't a replacement for a relational database that owns your source of truth. Teams commonly keep records in PostgreSQL or MySQL and index a searchable copy in Elasticsearch. Against a bare Lucene library it adds distribution, a REST API, and cluster management; against a plain SQL LIKE query it adds an inverted index, analyzers, and relevance scoring that a scan can't match. Knowing where each tool fits is itself an interview signal.
| System | Best at | Consistency model | Watch out for |
|---|---|---|---|
| Elasticsearch | Full-text search, log and metric analytics, relevance ranking | Eventually consistent, near real time | Not a system of record, no multi-document transactions |
| PostgreSQL / MySQL | Transactional records, joins, ACID writes | Strong (ACID) | Full-text search is slower and less flexible at scale |
| Apache Solr | Full-text search on Lucene, mature faceting | Near real time | Smaller managed-cloud ecosystem than Elastic |
| Apache Lucene (library) | Embeddable indexing and search | Single process only | No distribution, sharding, or REST layer on its own |
Prepare in layers, and practice out loud. Most Elasticsearch rounds move from concept questions to hands-on query writing to a cluster design or debugging discussion, so rehearse each stage rather than only reading answers.
The typical Elasticsearch interview flow
Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Elasticsearch questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works