This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Identity, Image, and Privacy Notice
To respect individual privacy, some names, profile photographs, avatars, biographical details, and other identifying information displayed in this guide may be replaced with pseudonyms, licensed stock images, illustrative avatars, composite images, or representative descriptions. Unless a person is expressly identified as an actual contributor, a displayed name, image, or profile should not be understood as depicting or identifying a specific candidate, interviewer, employee, or other real individual. These representations are provided for editorial and illustrative purposes only and do not imply endorsement, employment, participation, or affiliation with this guide or any company mentioned in it. Any resemblance to an actual person is coincidental.
Company Notice
This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.
Questions or comments?
Contact us for general questions, or share feedback, technical corrections, and comments with the community.
21. Implement greedy decoding and beam search.CodingHardMicrosoft
i Question Details
Given next_logprobs(prefix_tokens), an EOS token, an initial prompt, and max_len, implement greedy decoding and beam search with beam width k. Use cumulative log-probabilities, expand unfinished beams, preserve finished beams, stop when all beams finish or max_len is reached, and apply deterministic tie-breaking.
Short Interview Answer (30-60 seconds)
I implement greedy decoding by keeping one prefix and choosing the highest next-token log-probability at each round. Equal scores are broken lexicographically. Beam search keeps the best k prefixes by cumulative log-probability. It preserves finished beams and expands only unfinished ones. Both methods stop at EOS or max_len. Greedy takes O(T × V) time and O(1) auxiliary space, ignoring the output. Beam search takes O(T × k × V × log(kV)) time and O(kV) auxiliary space.
We are building a token sequence one token at a time. The goal is to choose good next tokens and stop when we generate EOS or use max_len rounds. Greedy decoding keeps only one path. Beam search keeps the best k paths. This gives beam search a chance to recover when the best immediate choice does not lead to the best complete sequence.
Useful Questions to Ask the Interviewer
What input sizes, value ranges, and edge cases should the solution handle?
What output should be returned for empty, invalid, or duplicate input?
Should I prioritize execution time or memory use, and may I use the standard library?
How to Explain It in an Interview
1. Understand the input and required output
The input contains four main parts.
next_logprobs(prefix_tokens) receives the current token prefix. It returns possible next tokens and their log-probabilities.
eos is the token that marks the end of a sequence.
prompt is the starting token list.
max_len is the maximum number of new tokens that may be generated.
Beam search also receives k. This is the maximum number of beams kept after each round.
Both functions return the generated token list, including the original prompt.
2. Choose the decoding methods
Greedy decoding keeps one running prefix. At each round, it selects the token with the highest next-token log-probability.
Beam search keeps up to k prefixes. Each prefix is called a beam. A beam stores three values:
The token sequence.
Its cumulative log-probability.
A flag that says whether EOS has been generated.
Cumulative log-probability means that we add the log-probability from every generated token. A higher value is better. For negative scores, the value closer to zero is better.
When scores tie, the code chooses the lexicographically smaller token or token sequence. This makes the output deterministic.
3. Initialize the state
Greedy decoding starts with:
tokens = ["<s>"]
Beam search starts with one beam:
(["<s>"], 0.0, False)
The score is 0.0 because no new token has been generated yet. The finished flag is False because the sequence has not generated EOS.
The main beam-search invariant is this: after each round, the beam list contains the best k sequences under cumulative-score ranking and lexicographic tie-breaking.
4. Walk through the exact example
The example uses:
prompt = ["<s>"]
eos = "[EOS]"
max_len = 3
k = 2
For the prefix ["<s>"], the next-token values are:
A = -0.10
B = -0.10
[EOS] = -1.50
Greedy decoding sees that A and B have equal scores. It selects A because A is lexicographically smaller than B. Its cumulative score is now -0.10.
For ["<s>", "A"], the choices are C = -0.05 and [EOS] = -0.70. Greedy selects C because -0.05 is higher. The cumulative score becomes -0.15.
For ["<s>", "A", "C"], the next token is [EOS] with -0.80. Greedy appends it and stops. The returned sequence is ["<s>", "A", "C", "[EOS]"]. Its final score is -0.95.
Beam search keeps two paths after the first round:
["<s>", "A"] with -0.10
["<s>", "B"] with -0.10
The tie is ordered lexicographically.
During round two, the A beam creates:
["<s>", "A", "C"] with -0.15
["<s>", "A", "[EOS]"] with -0.80
The B beam creates:
["<s>", "B", "D"] with -0.11
["<s>", "B", "[EOS]"] with -0.70
The best two candidates are ["<s>", "B", "D"] with -0.11 and ["<s>", "A", "C"] with -0.15.
During round three, the B path adds [EOS] with -0.02. Its final cumulative score becomes -0.13.
The A path adds [EOS] with -0.80. Its final cumulative score becomes -0.95.
Both retained beams are now finished. Beam search stops and returns ["<s>", "B", "D", "[EOS]"] with -0.13.
The beam-search result is better in this example because -0.13 is greater than -0.95.
5. Explain why the methods are correct
Greedy decoding correctly follows its rule. At each round, it selects the highest-scoring next token. It uses the required lexicographic rule when scores tie.
Beam search expands every unfinished retained beam. It carries finished beams forward without expanding them again. It calculates each candidate's cumulative score, sorts all candidates by the required ranking rule, and keeps the best k.
Therefore, after every round, the retained beam list contains the highest-ranked k candidates allowed by the beam width.
6. Explain the Python implementation
The greedy function copies the prompt into tokens. It performs at most max_len rounds. During each round, it calls next_logprobs, selects the best token, appends it, and stops if it is EOS.
The selection key is (-scores[token], token). Negating the score places higher scores first. The token text handles equal scores lexicographically.
The beam-search function stores beams as tuples containing tokens, score, and finished status.
At the start of each round, it stops if every retained beam is finished. It copies finished beams into the candidate list without expanding them. It expands unfinished beams with every token returned by next_logprobs.
It sorts candidates using (-score, tuple(tokens)) and keeps candidates[:k]. At the end, it prefers finished beams. If none of the retained beams finished, it returns the best current beam.
7. Explain complexity and edge cases
Let T be max_len. Let V be the number of tokens returned by next_logprobs.
Greedy decoding examines up to V choices during each of T rounds. Its time complexity is O(T × V). Its auxiliary space is O(1) when the returned token list is not counted.
Beam search may generate up to kV candidates in one round. Sorting them costs O(kV × log(kV)). Across T rounds, the time complexity is O(T × k × V × log(kV)). The diagram reports O(kV) auxiliary candidate space and O(k) retained beams.
Relevant edge cases include immediate EOS, equal token or beam scores, max_len being reached before EOS, and finished beams that must be preserved but never expanded again.
Key Insight / Why This Solution Works
The key idea is to compare a one-path local strategy with a limited multi-path strategy. Greedy decoding keeps one prefix and chooses the best immediate token. Beam search keeps up to k prefixes and ranks them by cumulative log-probability. Each beam stores its token sequence, cumulative score, and finished flag. The central invariant is that after every round, the beam list contains the best k candidates under score-first ranking with lexicographic tie-breaking. Finished beams remain eligible but are not expanded again.
Code
from typing importCallable, Dict, List, Tuple# The function receives a token prefix and returns:# next token -> log-probability.
LogProbFn = Callable[[Tuple[str, ...]], Dict[str, float]]
defgreedy_decode(
next_logprobs: LogProbFn,
eos: str,
prompt: List[str],
max_len: int,
) -> List[str]:
"""Decode by keeping only the best immediate next token."""# Copy the prompt so the caller's list is not changed.
tokens = list(prompt)
# Generate at most max_len new tokens.for _ inrange(max_len):
# Read the possible next tokens for the current prefix.
scores = next_logprobs(tuple(tokens))
# Higher log-probability wins.# Equal scores are broken by the token's lexicographic order.
next_token = min(
scores,
key=lambda token: (-scores[token], token),
)
# Extend the running sequence.
tokens.append(next_token)
# Stop as soon as the sequence generates EOS.if next_token == eos:
breakreturn tokens
defbeam_search(
next_logprobs: LogProbFn,
eos: str,
prompt: List[str],
max_len: int,
k: int,
) -> List[str]:
"""Decode while retaining the best k token sequences."""# Each beam is: (token sequence, cumulative score, finished flag).
beams: List[Tuple[List[str], float, bool]] = [(list(prompt), 0.0, False)]
# Generate at most max_len new tokens.for _ inrange(max_len):
# Stop when every retained beam has already generated EOS.ifall(done for _, _, done in beams):
break
candidates: List[Tuple[List[str], float, bool]] = []
for tokens, score, done in beams:
if done:
# Preserve a finished beam without expanding it again.
candidates.append((tokens, score, True))
continue# Expand this unfinished beam with every possible next token.for token, logp in next_logprobs(tuple(tokens)).items():
new_tokens = tokens + [token]
new_score = score + logp
is_finished = token == eos
candidates.append((new_tokens, new_score, is_finished))
# Put higher cumulative scores first.# Break equal scores by the full token sequence.
candidates.sort(key=lambda item: (-item[1], tuple(item[0])))
# Retain only the best k beams.
beams = candidates[:k]
# Prefer a finished retained beam when one exists.
pool = [beam for beam in beams if beam[2]] or beams
# Return the best beam using the same deterministic ranking rule.
best_tokens, _, _ = min(
pool,
key=lambda item: (-item[1], tuple(item[0])),
)
return best_tokens
defexample_next_logprobs(
prefix_tokens: Tuple[str, ...],
) -> Dict[str, float]:
"""Return the exact values used in the approved diagram."""
values: Dict[Tuple[str, ...], Dict[str, float]] = {
("<s>",): {
"A": -0.10,
"B": -0.10,
"[EOS]": -1.50,
},
("<s>", "A"): {
"C": -0.05,
"[EOS]": -0.70,
},
("<s>", "B"): {
"D": -0.01,
"[EOS]": -0.60,
},
("<s>", "A", "C"): {
"[EOS]": -0.80,
},
("<s>", "B", "D"): {
"[EOS]": -0.02,
},
}
return values[prefix_tokens]
if __name__ == "__main__":
prompt = ["<s>"]
eos = "[EOS]"
max_len = 3
beam_width = 2
greedy_result = greedy_decode(
example_next_logprobs,
eos,
prompt,
max_len,
)
beam_result = beam_search(
example_next_logprobs,
eos,
prompt,
max_len,
beam_width,
)
print("Greedy:", greedy_result)
print("Beam search:", beam_result)
# Expected output:# Greedy: ['<s>', 'A', 'C', '[EOS]']# Beam search: ['<s>', 'B', 'D', '[EOS]']
Time & Space Complexity
Let T be max_len. Let V be the number of tokens returned by next_logprobs. Greedy decoding checks up to V choices during each of T rounds, so its time complexity is O(T × V). It uses O(1) auxiliary space when the returned sequence is not counted. Beam search can create up to kV candidates per round and sorts those candidates. Its time complexity is O(T × k × V × log(kV)). Its auxiliary candidate space is O(kV), and it retains O(k) beams.
Where it is used
These methods are useful when software generates a sequence one item at a time. Examples include text generation, machine translation, speech recognition, image captioning, and autocomplete. Greedy decoding is useful when speed and low memory use matter. Beam search is useful when keeping several promising sequences can produce a better final result.
Why Interviewers Ask This
The interviewer is checking whether you understand the difference between a locally best choice and a limited search across several promising paths. They want to see correct cumulative scoring, careful handling of finished beams, deterministic tie-breaking, and the correct stopping conditions. They also evaluate whether your code matches your explanation and whether you can explain the time and auxiliary space costs accurately.
Common interview mistakes
A common mistake is ranking beams by only the newest token score instead of the full cumulative score. Another mistake is expanding a beam after it has generated EOS. Some implementations drop finished beams instead of preserving them. Candidates may also forget deterministic tie-breaking, which can make equal scores produce unstable results. Another mistake is stopping when only one beam finishes instead of stopping when all retained beams finish or max_len is reached.
Interview tip
Before coding, state the beam invariant clearly: after every round, keep the best k sequences by cumulative score, preserve finished beams, expand only unfinished beams, and use the same tie-break rule everywhere.
Interviewer may ask next
What happens when the beam width k is 1?
Beam search keeps only one candidate after each round. With the same ranking and tie-breaking rules, it behaves like greedy decoding in this example. It chooses A at the first tie, then C, then EOS. The result is ["<s>", "A", "C", "[EOS]"] with score -0.95. With the current sorting implementation, the time is O(T × V × log V), and the auxiliary candidate space is O(V).
How could we reduce the cost of sorting all beam candidates?
We could keep the best k candidates in a size-k heap while candidates are generated. The candidate-selection work per round would change from O(kV × log(kV)) to O(kV × log k). The total time would become O(T × k × V × log k). Correctness is preserved by using the same cumulative score and lexicographic tie-break in the heap ranking. The tradeoff is more complicated code.
22. Extract a stream prefix before a stop token.CodingHardMicrosoft
i Question Details
Consume a large text stream incrementally and return or emit only the content before the first occurrence of a specified stop token. The token may span chunk boundaries, and the solution must use bounded memory.
Short Interview Answer (30-60 seconds)
I keep a pending buffer for characters that may still form the stop token. For each chunk, I append it, search for the first token, and stop immediately if it appears. Otherwise, I emit everything except the final m - 1 characters. Those characters may combine with the next chunk. This handles tokens split across boundaries. The shown overlap search takes O(n + k·m) time. It retains O(m) carry-over state, while the Python concatenation may temporarily use O(c + m) memory.
The input is a sequence of text chunks and a non-empty stop token. We must emit only the content before the token’s first occurrence. The token may begin in one chunk and finish in the next. The solution keeps a small unresolved suffix between chunks instead of storing the full stream.
Useful Questions to Ask the Interviewer
What input sizes, value ranges, and edge cases should the solution handle?
What output should be returned for empty, invalid, or duplicate input?
Should I prioritize execution time or memory use, and may I use the standard library?
How to Explain It in an Interview
1. Understand the input and required output
The function receives an iterable of string chunks and a stop token. It yields pieces of the text before the first complete token.
For the diagram’s example, the chunks are ["Hello world:", ":discard the rest"]. The stop token is "::". The final output is "Hello world".
The function stops as soon as it finds the first token. It does not emit the token or anything after it.
2. Keep only the unresolved suffix
Let m be the length of the stop token. After an unsuccessful search, the algorithm keeps the last m - 1 characters.
Those characters are unresolved. They may be the beginning of a token that finishes in the next chunk. All earlier characters are safe to emit.
The central invariant is that the retained pending state contains only the unresolved suffix that may still begin the first stop token.
3. Initialize the state
The algorithm starts with pending = "".
For stop_token = "::", m is 2. Therefore, keep = m - 1 = 1.
Processing starts with the first chunk.
4. Walk through the exact example
Before the first chunk, pending is "". The algorithm appends "Hello world:", so pending becomes "Hello world:".
It checks pending.find("::"). The result is -1, so the full token is not present.
The algorithm keeps the final one character because keep is 1. That character is ":". It emits the safe prefix "Hello world". The new pending state is ":".
Next, the algorithm appends ":discard the rest". Pending becomes "::discard the rest".
It checks pending.find("::") again. The result is 0. The token begins at the first character of pending.
There is no additional text before the token, so the function emits nothing more and returns immediately. No later content is processed.
The final joined output is "Hello world". Two chunks were processed.
5. Explain why the result is correct
The algorithm does not emit the last m - 1 unresolved characters until it knows they are not part of the first stop token.
Therefore, every emitted character is definitely before that token. When the token is found at index i, pending[:i] is exactly the remaining text before its first occurrence.
6. Explain the Python implementation
The code first rejects an empty stop token. It initializes pending and calculates keep.
For each chunk, it appends the chunk to pending and searches for the token with str.find.
If the token is found, the code yields pending[:stop_index] when that slice is non-empty. It then returns immediately.
If the token is not found, safe_count tells us how many characters can be emitted. The code yields that safe prefix and retains only the final keep characters.
If the stream ends without the token, the code yields the remaining pending text.
7. Explain complexity and edge cases
Let n be the number of processed characters, k be the number of processed chunks, and m be the token length. The overlap-buffer analysis shown in the diagram is O(n + k·m) time.
The retained carry-over state between chunks is O(m). In this exact Python implementation, pending += chunk creates a temporary combined string. If c is the largest chunk length, peak temporary memory can be O(c + m). The bounded state carried from one chunk to the next remains O(m).
Important cases include a token at the beginning, a token that never appears, a token longer than one chunk, a token split across chunk boundaries, and a token with repeated prefix patterns such as "abab".
Key Insight / Why This Solution Works
The key insight is that only the final m - 1 characters can still become the beginning of a token that crosses a chunk boundary. When no token is found, the algorithm emits every earlier character and carries only that unresolved suffix forward. The invariant is that the retained pending state contains only characters that have not yet been proved safe to emit. This lets the program process a large stream incrementally and stop as soon as the first token appears.
Code
from collections.abc import Iterable, Iterator
defemit_prefix_before_stop(
chunks: Iterable[str],
stop_token: str,
) -> Iterator[str]:
# An empty token would match at every position.ifnot stop_token:
raise ValueError("stop_token must not be empty")
# pending stores text that is not yet safe to emit.
pending = ""# Keep enough characters to detect a token split across chunks.
keep = len(stop_token) - 1# Read the stream one chunk at a time.for chunk in chunks:
# Join the unresolved suffix with the current chunk.
pending += chunk
# Find the first complete stop token in the combined text.
stop_index = pending.find(stop_token)
# Stop immediately when the first token is found.if stop_index != -1:
# Emit only the remaining text before the token.if stop_index > 0:
yield pending[:stop_index]
return# Everything except the last keep characters is safe.
safe_count = len(pending) - keep
if safe_count > 0:
# Emit the safe prefix.yield pending[:safe_count]
# Retain only the unresolved suffix for the next chunk.
pending = pending[safe_count:]
# If no stop token appeared, emit the remaining text.if pending:
yield pending
if __name__ == "__main__":
chunks = ["Hello world:", ":discard the rest"]
stop_token = "::"
result = "".join(emit_prefix_before_stop(chunks, stop_token))
print(result) # Hello world
Time & Space Complexity
Let n be the number of characters processed before the function stops, k be the number of chunks processed, and m be the stop token length. The overlap-buffer analysis is O(n + k·m) time because every chunk is searched together with at most m - 1 carried characters. The retained buffer between chunks is O(m). In the exact Python code, concatenating pending and the current chunk creates a temporary string, so peak temporary memory is O(c + m), where c is the largest processed chunk. Yielded output is not counted as auxiliary memory.
Where it is used
This pattern is useful for streamed files, network responses, logs, protocol messages, and generated text. It lets a program stop at a delimiter without storing the complete stream. It is especially useful when the delimiter may be divided across two input chunks.
Why Interviewers Ask This
The interviewer is checking whether you can process streaming data without storing the full input. They want to see whether you handle a token that crosses chunk boundaries, maintain a correct invariant, and stop at the first match. They are also evaluating whether the code follows the explanation, whether edge cases are handled, and whether you distinguish retained bounded state from temporary memory created by Python string operations.
Common interview mistakes
One mistake is searching each chunk separately. That misses a token split across chunk boundaries. Another mistake is emitting the whole pending string when the token is absent. The final m - 1 characters must be retained. Some candidates keep the complete stream, which breaks the bounded carry-over requirement. Others continue reading after finding the first token. It is also incorrect to allow an empty stop token without defining special behavior or to claim that the exact Python implementation has only O(m) peak memory without discussing the temporary combined string.
Interview tip
State the invariant first: the retained pending state contains only the unresolved suffix that may still begin the token. Then use the two colons in the example to show why the final colon of the first chunk cannot be emitted yet.
Interviewer may ask next
What happens if the stop token never appears?
The function continues emitting every safe prefix. When the input ends, it yields the remaining pending suffix. The complete input is therefore produced. The overlap-buffer time remains O(n + k·m). The retained carry-over state remains O(m), while the exact Python concatenation may temporarily use O(c + m) memory.
How would you make the strict peak auxiliary memory O(m) even when a chunk is very large?
Avoid creating pending + chunk as one large temporary string. Search for matches inside the current chunk and separately check the small boundary made from pending and at most the first m - 1 characters of the chunk. Emit safe slices directly and retain only the final m - 1 characters. Correctness is preserved because every possible cross-boundary token start remains in the small overlap. The time can remain O(n + k·m), retained and peak auxiliary state becomes O(m), and the tradeoff is more complicated code.
23. Design a search API for finding local service providers.API DesignHardMicrosoft
i Question Details
Design the API and serving flow for a query such as finding plumbers near a location. Cover provider-data ingestion, geographic and inverted indexes, semantic matching, ranking, filtering, pagination, and freshness.
Short Interview Answer (30-60 seconds)
At a high level, I would build one search flow for nearby service providers. The client sends an HTTPS request to the API Gateway, which validates the API key or JWT and applies rate limits. The Search Orchestrator checks Redis first. On a cache miss, it searches the geographic index, keyword index, and semantic matching service. It combines provider candidates, ranks them with profile signals, then applies filters and pagination. The API returns JSON results. The main trade-off is better search quality and speed, but more indexes must remain fresh.
Detailed Explanation
The goal is to return useful local providers for a query such as "plumbers near me." The main challenge is combining location, keywords, meaning, ranking, filters, and fresh provider data. I would explain the design by following the serving and ingestion flows in the diagram.
Useful Questions to Ask the Interviewer
Which clients and core use cases must the API support?
What authentication, authorization, and data-validation rules should I assume?
What scale, error handling, idempotency, and versioning requirements matter?
How to Explain It in an Interview
1. Start with the client and API boundary
I would start with the user request. The Client App sends an HTTPS request to the API Gateway / Search API. The request includes the search text and location context.
The gateway validates the API key or JWT. A JWT is a signed token that carries caller information. The gateway also applies rate limiting, which stops one caller from sending too many requests.
After validation, the gateway forwards the request to the Query Parser / Search Orchestrator. The final JSON response returns through the gateway to the Client App.
2. Parse the query and check the cache
The Query Parser / Search Orchestrator reads the query and understands its intent. For example, it separates the requested service from the location. It then builds a search plan.
Before running several searches, it checks the Result Cache, such as Redis. The cache stores search results using a normalized query and its filters.
A cache hit returns stored results to the orchestrator. A cache miss continues to the search indexes. This lowers response time for repeated queries. The downside is that cached results can become stale, so the update pipeline invalidates affected cache entries.
3. Find provider candidates
On a cache miss, the orchestrator uses three search paths.
The Geographic Index finds providers near the requested location. The Inverted Index matches words such as "plumbers" against provider services and related text. An inverted index maps each word to matching records, which makes keyword searches fast.
The Semantic Matching Service handles meaning instead of only exact words. It uses a Vector Index to compare the query with provider information in a numeric meaning space.
Each search path returns provider candidates. The orchestrator combines these candidate IDs before sending them to ranking.
4. Rank, filter, and paginate
The Ranking Service receives the combined candidate IDs. It reads ranking features from the Provider Profile Store, which is the source of truth for provider details.
The diagram shows signals such as ratings, reviews, distance, popularity, price level, availability, and recency. The Ranking Service uses these signals to order the candidates.
The ranked results move to Filter & Pagination. This component applies filters such as distance, rating, price, and open status. It then divides the result list into pages.
The paginated result is returned through the API Gateway as a JSON response to the Client App.
5. Keep provider data and indexes fresh
Provider feeds, administrators, and providers send updates into the Provider Data Ingestion & Freshness Pipeline.
The Ingestion / Validation / Normalization component validates data, removes duplicates, normalizes fields, geocodes addresses, and enriches records. The clean data is written to the Provider Profile Store.
The store sends an indexing event to the Index Updater / Event Stream. The diagram gives Kafka or Pub/Sub as examples. The event stream publishes create, update, and delete changes.
These events update the Geographic Index, Inverted Index, and Vector Index. They also trigger Result Cache invalidation. This asynchronous flow keeps the serving path fast while the searchable copies remain reasonably fresh.
6. Explain observability and the main trade-off
The main services send logs and metrics to Observability / Logging. This supports logs, metrics, traces, alerts, and dashboards.
The benefit of the design is fast search with strong relevance. Each index handles a different problem. The downside is operational complexity. Several searchable copies must stay aligned with the Provider Profile Store.
The design accepts short freshness delays because synchronous updates to every index would make provider writes slower and less reliable.
Practical Complexity & Trade-offs
The benefit is that each component has one clear job. The Geographic Index handles distance. The Inverted Index handles matching words. The Vector Index helps find similar meaning. Redis makes repeated searches faster. The downside is that provider data exists in several searchable copies. The Provider Profile Store may change before every index receives its update event. This creates a short period of stale results. The event stream reduces this problem by updating every index and invalidating related cache entries. More ranking signals can improve result quality, but they add read cost and tuning work. Rate limiting protects the API, but it may reject requests from callers that exceed the allowed rate. We accept these costs because local search needs low latency, useful ranking, flexible filters, and fresh provider information.
Why Interviewers Ask This
The interviewer is testing whether the candidate can turn a simple search request into a complete serving system. They want clear API boundaries, correct request and response flow, useful indexes, sound data ownership, and a realistic freshness plan. They also evaluate judgment about caching, semantic search, ranking, filtering, pagination, rate limiting, observability, and asynchronous updates. A strong answer explains why each component exists and clearly states the main trade-offs.
Interviewer may ask next
How would this design handle a large traffic spike for a popular location?
I would keep the same request flow and scale the existing serving components. The API Gateway would continue validating API keys or JWTs and applying rate limits. The Result Cache would become especially important because many users may repeat similar searches for the same location and filters.
A higher cache hit rate reduces calls to the Search Orchestrator, indexes, Ranking Service, and Provider Profile Store. The API Gateway, orchestrator, and Ranking Service can run more instances behind their existing service boundaries. The search indexes can add capacity using the scaling features of their chosen implementations.
Observability should track request latency, cache hit rate, rate-limited requests, index errors, and ranking delays. These signals show which component needs more capacity.
The main downside is higher infrastructure cost. More aggressive caching can also return slightly older results. The security checks, candidate search paths, ranking signals, filters, pagination, ingestion flow, and cache invalidation design remain unchanged.
What happens when a provider changes its address or availability?
The change follows the existing ingestion and freshness flow. Provider Feeds / Admin / Provider Updates sends the new data to Ingestion / Validation / Normalization.
That component validates the fields, removes duplicates, normalizes the values, and geocodes the new address when needed. It then writes the clean record to the Provider Profile Store, which remains the source of truth.
The store sends an indexing event through the Index Updater / Event Stream. An address change updates the Geographic Index. A service or description change updates the Inverted Index and Vector Index when applicable. The same update flow invalidates affected Result Cache entries.
The main downside is eventual consistency. The Provider Profile Store may contain the new value before every index processes the event. During that short window, a search may use older index data. Logs, metrics, traces, and alerts should expose delayed or failed updates. The normal search, ranking, filtering, pagination, and API security flow remains unchanged.
24. Design the API for a Pastebin-like service.API DesignMediumMicrosoft
i Question Details
Define the endpoints for creating and retrieving pastes, and explain the supporting storage, caching, identifiers, expiration behavior, rate limits, and scaling choices.
Short Interview Answer (30-60 seconds)
At a high level, I would support two main flows: creating a paste and reading a paste. The client sends POST /pastes through the API Gateway and Rate Limiter. The Paste API Service gets a short ID, stores the text in object storage, and saves metadata in PostgreSQL. For reads, GET /{id} first checks the CDN and Redis. A cleanup worker removes expired data and cached copies. The trade-off is more cache and cleanup complexity in exchange for faster reads and easier horizontal scaling.
Detailed Explanation
The goal is to create and retrieve text pastes through short identifiers. The main challenge is serving popular reads while enforcing expiration correctly. I will explain the design by following the flows shown in the diagram.
Useful Questions to Ask the Interviewer
Which clients and core use cases must the API support?
What authentication, authorization, and data-validation rules should I assume?
What scale, error handling, idempotency, and versioning requirements matter?
How to Explain It in an Interview
1. Define the API boundary
I would begin with two main endpoints. The client uses POST /pastes to create a paste. It uses GET /{id} to retrieve one.
The Browser or Mobile Client sends uncached requests through the API Gateway and Load Balancer. The gateway routes traffic to the service layer. The Rate Limiter admits or throttles requests before they reach the Paste API Service.
Protected pastes may also use the shown Auth or API key check. This check is attached to the API layer. Public pastes can use the normal read path through the CDN.
2. Create a paste
The create flow starts with HTTPS POST /pastes. The request reaches the API Gateway and then passes through the Rate Limiter. After admission, one stateless Paste API Service replica handles the request.
The service asks the ID Generator for a short ID. The ID Generator returns the short ID to the service. The service writes the paste body to Blob or Object Storage. It then inserts the paste metadata and expiration information into PostgreSQL.
The metadata connects the short ID to the stored object. It also holds the expiration information needed during reads and cleanup. The service may place the new paste in Redis as an optional recent cache.
After the write succeeds, the client receives 201 Created, the paste ID, and the short URL.
3. Serve a cached read
The read flow starts with HTTPS GET /{id}. The request first reaches the CDN or Edge Cache.
If the CDN already has the public paste, it returns a 200 cached response directly to the client. This avoids work in the API service, PostgreSQL, and object storage.
This path is useful because a small number of popular pastes may receive many repeated reads. The CDN absorbs those hot reads close to the client.
4. Handle a cache miss
If the CDN misses, it forwards the request to the API Gateway. The request passes through the Rate Limiter and reaches a Paste API Service replica.
The service first checks Redis Cache. Redis returns either a hit or a miss. On a Redis hit, the service can use the cached paste.
On a Redis miss, the service looks up metadata by ID in PostgreSQL. PostgreSQL returns the object key and time-to-live information. The service then reads the paste body from Blob or Object Storage.
After reading the paste, the service places the result in Redis. It also caches the public paste at the CDN. The client then receives 200 OK with the paste content.
5. Remove expired pastes
Expiration runs outside the normal request path. The Expiration Scheduler or Cleanup Worker finds expired pastes and receives their IDs.
The worker deletes the stored content from Blob or Object Storage. It deletes or tombstones the related metadata. A tombstone is a marker showing that the record is no longer active.
The worker also evicts Redis keys and purges the edge cache. This step is important because deleting only the database record could leave an expired cached copy available.
The trade-off is extra background work. Cleanup must coordinate several stored copies of the same paste.
6. Scale and observe the system
The Paste API Service runs as stateless replicas. Stateless means request data is not kept inside one server between calls. The Load Balancer can therefore send a request to any healthy replica.
The CDN handles hot public reads. Redis reduces repeated database and storage lookups. PostgreSQL stores metadata, while object storage keeps the paste body.
The system emits telemetry to the Logs, Metrics, and Alerts platform. Telemetry means operational data used to find errors, measure load, and trigger alerts.
The benefit is fast reads and simple horizontal scaling. The downside is more components and harder cache invalidation.
Practical Complexity & Trade-offs
The benefit of this design is that each component has a clear job. The API Gateway routes requests. The Rate Limiter protects the service from too much traffic. PostgreSQL stores metadata, while object storage keeps the paste body. Redis and the CDN make repeated reads faster. The downside is more operational work. Cached copies can become stale, so the cleanup worker must remove them after expiration. The ID Generator must also avoid duplicate short IDs. Stateless API replicas are easy to scale because any replica can handle a request. However, all shared state must remain in PostgreSQL, Redis, or object storage. We accept this complexity because Pastebin traffic is often read-heavy. Faster cached reads reduce latency and lower pressure on the storage systems.
Why Interviewers Ask This
Interviewers use this question to test practical API design judgment. They want clear endpoint choices and correct request and response flows. They also check whether the candidate separates metadata from content, uses caching carefully, handles expiration, and applies rate limits. A strong answer explains horizontal scaling and operational risks without claiming perfect behavior. The key skill is connecting each component to a real requirement and explaining the trade-offs clearly.
Interviewer may ask next
How would this design handle a paste that suddenly becomes very popular?
I would keep the same API and rely more heavily on the existing cache path. The affected endpoint is GET /{id}. The first request may miss the CDN and Redis, so the Paste API Service reads metadata from PostgreSQL and the body from object storage. It then fills Redis and caches the public response at the CDN. Later requests can be served from the edge without reaching the origin services. This protects the API replicas, PostgreSQL, and object storage from repeated work. The Paste API Service can also add more stateless replicas behind the Load Balancer. The Rate Limiter still protects the service during a sudden burst. Correctness depends on keeping the cached paste aligned with its expiration time. The cleanup worker must purge Redis and the CDN when the paste expires. The main downside is cache invalidation complexity. A delayed purge could briefly leave stale content available, so cleanup failures must be visible through logs, metrics, and alerts.
How would you make sure expired pastes are removed from every storage and cache layer?
I would keep expiration information in PostgreSQL and use the existing Expiration Scheduler or Cleanup Worker. The affected flow includes PostgreSQL, Blob or Object Storage, Redis Cache, and the CDN. The worker finds expired pastes and receives their IDs. It deletes the stored body, deletes or tombstones the metadata, evicts the Redis keys, and purges the edge cache. This keeps the stored copies aligned with the expiration rule. The normal GET /{id} flow remains unchanged. It still checks the CDN, Redis, PostgreSQL, and object storage in that order when needed. Logs, metrics, and alerts should record cleanup failures because one failed step can leave stale data behind. The main downside is that cleanup depends on background processing. A large expiration backlog can delay deletion. The worker must therefore scale with the number of expiring pastes, while the rest of the create and read architecture remains unchanged.
25. Design a secure Copilot API.API DesignHardMicrosoft
i Question Details
Design a secure API for a multi-tenant enterprise AI copilot. Cover end-user and service authentication, authorization across tenants and tools, token handling, replay and theft protection, abuse controls, tenant isolation, and reliability.
Short Interview Answer (30-60 seconds)
At a high level, I would secure the Copilot API in layers. Human users and service clients first receive short-lived JWTs from the Enterprise IdP / OAuth Server. Requests pass through abuse controls, the API Gateway, token validation, and replay checks. The Copilot API / Orchestrator then uses tenant-aware authorization before calling the model or any approved tool. Tool calls use ephemeral credentials and tenant-scoped resources. Failures can produce a fallback or partial result. The trade-off is stronger security and isolation, with extra latency and operational complexity.
Detailed Explanation
The goal is to provide a secure Copilot API for many enterprise tenants. The main challenge is protecting identities, tools, data, and responses together. I would explain the design by following the diagram from login to the final response.
Useful Questions to Ask the Interviewer
Which clients and core use cases must the API support?
What authentication, authorization, and data-validation rules should I assume?
What scale, error handling, idempotency, and versioning requirements matter?
How to Explain It in an Interview
1. Authenticate users and service clients
I would start by separating human and service authentication. The End User performs an OIDC login with the Enterprise IdP / OAuth Server. OIDC is a standard login method for applications. The identity provider returns a short-lived JWT. A JWT is a signed token containing identity and security claims.
The Service Client / Enterprise App uses client credentials with mTLS. mTLS encrypts the connection and lets both sides verify each other. The identity provider returns a short-lived service JWT. Short token lifetimes reduce the damage caused by token theft.
2. Screen requests at the platform edge
The End User sends HTTPS with JWT and DPoP or nonce proof. The Service Client / Enterprise App sends mTLS with JWT. Both request paths first reach WAF / Rate Limiter / Abuse Detection.
This component throttles heavy callers and detects unusual traffic. It sends abuse events to Audit Logs / SIEM. Only a screened request moves to the API Gateway.
The API Gateway owns routing, request shaping, and policy enforcement. It does not trust a token only because the caller supplied one.
3. Validate tokens and stop replay
The API Gateway asks Token Validator / JWKS to validate the token. JWKS contains public signing keys used to verify JWT signatures. The validator checks the signature, issuer, audience, and expiration. It returns a token-valid result to the gateway.
The gateway also asks Replay Cache (nonce / jti) to check for reuse. A nonce is a one-time value. A jti is a unique token identifier. The cache returns either okay or reject replay.
This control protects against replay and stolen-token reuse. Reading a jti alone is not enough. The platform must remember previous use and reject repeated use.
4. Authorize the tenant and requested tool
After authentication succeeds, the API Gateway sends the authenticated request and tenant context to Copilot API / Orchestrator. The orchestrator asks Authorization Policy Engine to authorize the user or service and requested tool scope.
Authentication proves who the caller is. Authorization decides what that caller may do. The policy engine resolves tenant policy through Tenant Metadata / RBAC / Allowed Tools / Quotas. RBAC means permissions are assigned through roles.
The metadata component returns roles, allowed tools, and quota information. The policy engine then returns allow or deny to the orchestrator. This prevents callers from using tools or entitlements belonging to another tenant.
5. Call the model and approved tools
The orchestrator sends the prompt and safe context to Model Service. Model Service returns model output to the orchestrator.
When a tool is required, the orchestrator sends an approved invocation to Tool Access Proxy. The proxy must never bypass policy approval. It asks Secrets Manager / KMS for a delegated token or secret.
Secrets Manager / KMS returns an ephemeral credential. Ephemeral means short-lived and limited to the current need. The proxy then sends a scoped API or data request to Tenant-Scoped Tools & Data. These resources are isolated per tenant.
The tool or data result returns to Tool Access Proxy. The proxy then returns the tool result to the orchestrator.
6. Handle failures, return responses, and record activity
If a tool call times out or fails, Tool Access Proxy sends a failure signal to Retry / Circuit Breaker / Fallback. That component can return a fallback or partial result to the orchestrator. This improves reliability without pretending the failed tool succeeded.
The orchestrator sends the final response to the API Gateway. The gateway returns the response to the originating End User or Service Client / Enterprise App.
Audit Logs / SIEM receives abuse events from WAF / Rate Limiter / Abuse Detection. It also receives authentication and access logs from the API Gateway. The orchestrator records tool usage, authorization decisions, and failures. The audit system supports centralized logging, monitoring, and alerting. It does not create or own the business response.
The benefit is layered protection and strict tenant isolation. The downside is more network hops, policy management, and operational work.
Practical Complexity & Trade-offs
The design uses several controls because one security check is not enough. The benefit is that replayed requests, stolen tokens, unsafe tools, and cross-tenant access become harder. The downside is extra latency and more services to operate. Token validation needs current JWKS signing keys. Replay protection needs storage for used nonce or jti values. Tenant authorization needs correct roles, allowed-tool rules, and quotas. Ephemeral credentials reduce risk, but they must be requested more often. Tool Access Proxy adds another network hop, but it creates one place to enforce tool policy. Retry and fallback improve reliability, but repeated calls may add load. We accept this complexity because enterprise tools and data require strong isolation.
Why Interviewers Ask This
Interviewers ask this question to test engineering judgment, not product memorization. They want clear separation between authentication and authorization. They also check whether request and response flows are modeled correctly. A strong answer explains token validation, replay protection, tenant isolation, tool approval, secret handling, abuse controls, audit ownership, and failure behavior. The candidate should also explain why each layer exists and what operational cost it adds.
Interviewer may ask next
What happens when an approved tool call times out or fails?
Tool Access Proxy sends the timeout or failure signal to Retry / Circuit Breaker / Fallback, as shown in the design. That component can apply limited retry, circuit-breaker, or fallback behavior. It may then return a fallback or partial result to Copilot API / Orchestrator. The orchestrator must treat that result as incomplete. It should not present missing tool data as confirmed information. The unchanged security path still applies. The caller remains authenticated, Authorization Policy Engine still approves the tool scope, and the proxy still uses an ephemeral credential. Failure activity should reach Audit Logs / SIEM through the orchestration logging flow. The final response returns through the API Gateway to the original caller. The main downside is reduced answer quality during dependency failures. Retries can also increase latency and load, while a circuit breaker may temporarily stop calls to a recovering dependency.
How does the design stop an attacker from reusing a stolen JWT?
The design uses several checks before the request reaches Copilot API / Orchestrator. WAF / Rate Limiter / Abuse Detection first screens unusual traffic. The API Gateway then asks Token Validator / JWKS to verify the token signature and claims. This catches invalid, expired, or incorrectly issued tokens. A correctly signed stolen token might still pass that check. The gateway therefore also asks Replay Cache to check the nonce or jti. The cache remembers previous use and returns reject replay when the same proof appears again. DPoP or nonce proof also makes captured user requests harder to repeat from another client. Short-lived JWTs reduce the remaining attack window. A rejected request never reaches Authorization Policy Engine, Model Service, or Tool Access Proxy. Related abuse and access activity is recorded in Audit Logs / SIEM. The downside is that replay protection needs shared state, storage capacity, and careful expiry of cache entries.
26. Design APIs for a scalable multi-channel OTP service.API DesignHardMicrosoft
i Question Details
Define APIs for generating, delivering, validating, and expiring one-time passwords over channels such as SMS, WhatsApp, and email. Address uniqueness per user and request, multiple clients, replay prevention, idempotency, failures, and traffic spikes.
Short Interview Answer (30-60 seconds)
At a high level, I would expose secure APIs for generating and validating OTPs. Mobile, web, and partner clients call POST /otp/generate or POST /otp/validate through the API Gateway. The gateway handles authentication and rate limiting. The OTP API Service checks idempotency, stores a hashed OTP with expiry and attempt state, and prevents reuse. Delivery runs asynchronously through a queue and orchestrator to SMS, WhatsApp, or email. The trade-off is more operational complexity, but the design handles traffic spikes, provider failures, retries, and fallback better.
Detailed Explanation
The goal is to generate, deliver, validate, and expire OTPs across several channels. The main challenge is preventing duplicates and replay while handling provider failures and traffic spikes. I would explain the system by following the request and response paths in the diagram.
Useful Questions to Ask the Interviewer
Which clients and core use cases must the API support?
What authentication, authorization, and data-validation rules should I assume?
What scale, error handling, idempotency, and versioning requirements matter?
How to Explain It in an Interview
1. Define the client and API boundary
I would begin with the three supported client types. They are Mobile App, Web App, and Internal Service / Partner App. Each client sends HTTPS requests using JWT or client credentials. Requests first reach the API Gateway / Auth + Rate Limit component. The gateway checks the caller and controls excessive traffic. Accepted requests continue to the OTP API Service. Each API response returns through the gateway to the client that made the request.
2. Generate an OTP safely
The client sends POST /otp/generate through the gateway. The OTP API Service checks the Idempotency + Request Store before creating new work. It uses idempotency_key, client_id, and request_id. Idempotency means that retrying the same request does not create another independent operation. The store returns whether the request is existing or new.
For a new request, the service stores the hashed OTP and its state in the OTP Store. The diagram includes expiry, attempt count, user_id, single-use state, and expires_at. The service then sends a delivery job to the Delivery Queue. The response returns to the gateway as 202 Accepted with request_id. This status means the request was accepted, but delivery continues asynchronously.
3. Deliver through SMS, WhatsApp, or email
The Delivery Queue protects the API during traffic spikes. It separates the fast API request from slower provider calls. The Delivery Orchestrator receives the queued work and selects a channel. It can send the OTP through the SMS Provider, WhatsApp Provider, or Email Provider.
Each provider returns a delivery response or status to the orchestrator. A provider can also send a delivery callback. The orchestrator owns channel selection, retries, and fallback. If one channel fails, it can retry or select another supported channel. This failure handling does not change the original request_id or create another independent OTP.
4. Validate and prevent replay
The client sends POST /otp/validate through the same gateway. The OTP API Service reads the current OTP state from the OTP Store. Validation uses the stored hashed OTP and checks its expiry, attempt count, and single-use state. The service then updates the OTP state when needed.
The response returns to the gateway as 200 Valid / Invalid. A successfully used OTP cannot be accepted again. This is replay prevention, which means someone cannot reuse an old valid code. Attempt tracking also limits repeated validation tries.
5. Expire old OTP records
The Expiry Worker / TTL Cleanup handles expired OTPs in the background. It expires or deletes old OTP state after the allowed lifetime. This prevents stale codes from remaining usable. It also keeps old records from growing without limit. Cleanup is separate from the synchronous generate and validate response paths.
6. Record logs and operating signals
The OTP API Service and Delivery Orchestrator send logs, traces, and metrics to Audit Logs + Metrics. These records help the team investigate failed requests, delivery problems, retry activity, and unusual validation attempts. The audit system observes the business flow. It does not create the client response or decide whether an OTP is valid.
7. State the main trade-off
This design adds stores, a queue, an orchestrator, and background processing. These components require monitoring and operational work. The benefit is better separation of responsibilities. API requests remain fast. Delivery can scale independently. Idempotency limits duplicate work. Single-use state reduces replay risk. Retries and fallback improve delivery success across several channels.
Practical Complexity & Trade-offs
The benefit is that each component has one clear job. The gateway handles caller checks and rate limits. The OTP API Service owns generation and validation. The Idempotency + Request Store prevents duplicate requests from creating separate work. The OTP Store keeps the hashed OTP, expiry, attempts, user state, and single-use state. The queue absorbs traffic spikes, while the orchestrator handles delivery, retries, and fallback. The downside is more operational complexity. More services must be monitored and maintained. Asynchronous delivery also means POST /otp/generate can return before the user receives the message. This is safer and more scalable, but it can add delivery delay. We accept this because external providers may be slow or temporarily unavailable.
Why Interviewers Ask This
Interviewers use this question to test engineering judgment rather than memorized endpoints. They want clear API boundaries, correct request and response directions, safe OTP state, and proper responsibility ownership. They also check whether the candidate understands idempotency, replay prevention, expiry, rate limiting, asynchronous work, and provider failures. A strong answer explains how the queue handles traffic spikes and how retries or fallback improve delivery. It should also describe the added operational cost honestly.
Interviewer may ask next
How would this design handle a sudden traffic spike?
I would keep the client-facing API contracts unchanged. Mobile, web, and partner clients still send POST /otp/generate through the API Gateway / Auth + Rate Limit component. The gateway limits excessive traffic before requests reach the OTP API Service. The service still checks the Idempotency + Request Store, writes the OTP state, and returns 202 Accepted with request_id.
The important scaling component is the Delivery Queue. It absorbs a burst of delivery jobs and lets the Delivery Orchestrator process them at a controlled rate. This prevents slow SMS, WhatsApp, or email providers from blocking API request handling. Correctness remains protected because repeated requests use the same idempotency checks. The OTP Store still tracks expiry, attempts, and single-use state.
The main downside is delay. A large queue may increase the time before the user receives the OTP. The validation API, replay prevention, provider callbacks, retry logic, fallback behavior, and audit flow remain unchanged.
What happens when the selected delivery provider fails?
The Delivery Orchestrator handles the failure without changing POST /otp/generate or POST /otp/validate. It sends the OTP to the selected SMS, WhatsApp, or email provider. The provider returns a delivery response or status. It may also send a later delivery callback.
When the result shows failure, the orchestrator applies its channel selection, retry, and fallback behavior. It can retry delivery or select another supported channel. The request continues to use the original request_id. The system does not create another independent OTP because the Idempotency + Request Store protects the original request. The OTP Store still keeps the same hashed OTP, expiry, attempt count, and single-use state.
Logs, traces, and metrics record the provider failure and the retry path. The main downside is added delivery time and possibly higher provider cost. The gateway, validation flow, expiry cleanup, and client response path remain unchanged.
27. How would you modify and safely roll out an existing public API?API DesignHardMicrosoft
i Question Details
Describe the release process for changing a public API already used by clients. Cover backward compatibility, versioning, testing, staged rollout, monitoring, rollback, deprecation, and client communication.
Short Interview Answer (30-60 seconds)
At a high level, I would introduce the API change without breaking existing clients. Requests still enter through the API Gateway. The Version Router sends normal v1 traffic to Stable API v1 and sends selected traffic to the New API Change. Before production, I run unit, contract, integration, and regression tests, followed by staging validation. Feature flags limit the first rollout to canary clients. Monitoring watches latency, errors, and adoption. If the release becomes unhealthy, rollback shifts traffic back to v1. The trade-off is slower delivery and more operational work, but much lower client risk.
Detailed Explanation
This question asks how we can change a public API that clients already use. We must add new behavior without suddenly breaking existing applications. The goals are to keep Stable API v1 working, test the change carefully, release it to a small group first, watch its health, and reverse it quickly when needed. I would explain the solution in the same order shown in the diagram.
Useful Questions to Ask the Interviewer
Is the change backward compatible, or does it need versioned behavior?
How many clients currently use Stable API v1?
Can selected clients join a canary or beta rollout?
Which latency and error limits should stop the rollout?
How long must Stable API v1 remain available?
How to Explain It in an Interview
1. Decide whether the change is compatible
I would first classify the API change. A backward-compatible change keeps current client requests working. For example, it may add optional behavior without changing the existing contract. A breaking change needs separate versioned behavior because older clients cannot safely use it.
The diagram keeps Stable API v1 running beside the New API Change. This allows existing clients to continue using the stable behavior. Selected or versioned requests can use the new behavior. This decision reduces the chance of forcing every client to update at once.
2. Test the new behavior before rollout
The New API Change moves through the Automated Test Pipeline before production exposure. Unit tests check small pieces of code. Contract tests check that the API behavior still matches client expectations. Integration tests check that connected parts work together. Regression tests check that existing behavior has not been damaged.
A validated build then moves to the Staging or Sandbox Environment. This environment is used to test the complete release before public traffic reaches it. Only a validated release becomes ready for rollout.
3. Keep one controlled production request path
API Clients send an HTTPS API request to the API Gateway or Public Endpoint. The gateway sends the request to the Version Router and Backward Compatibility Layer.
The router owns the traffic decision. It sends ordinary v1 traffic to Stable API v1. It sends flagged or versioned traffic to the New API Change. This compatibility layer lets old and new behavior operate at the same time.
Stable API v1 or the New API Change processes the request. The chosen API sends its API response back to the version router. The router returns the response to the gateway. The gateway then sends the HTTPS response back to the API client.
4. Release gradually with feature flags
After staging validation, Feature Flags and the Traffic Splitter control production exposure. A feature flag is a switch that enables the new behavior for selected traffic. The traffic splitter defines how much traffic reaches the new change.
The traffic policy is sent to the Version Router. Canary or beta clients receive limited exposure first. Their requests can reach the New API Change while normal clients continue using Stable API v1.
If the canary remains healthy, the team can increase exposure in small steps. This limits the number of clients affected by an early defect.
5. Monitor both API paths
The API Gateway sends access logs and metrics to Monitoring and Alerts. Stable API v1 and the New API Change send service logs and metrics to the same monitoring system.
The team watches latency, error rate, 4xx and 5xx responses, and client adoption. Latency shows whether responses are becoming slower. Error rates show whether requests are failing. Adoption shows how many clients have started using the new behavior.
Monitoring is a supporting flow. It does not return the business response to the client. Its job is to detect release problems and create alerts.
6. Roll back when the release is unhealthy
When monitoring detects a serious problem, it sends an alert to the Rollback Controller. The controller can disable the rollout or shift traffic back through the feature flags and traffic splitter.
It can also revert traffic to Stable API v1. The public request path remains available through the gateway and version router. Only the routing policy changes.
Rollback should happen before the new behavior reaches more clients. After traffic becomes stable, the team can investigate the logs, fix the issue, repeat the automated tests, and validate the corrected build in staging.
7. Communicate migration and deprecation
The Client Communication and Deprecation Plan explains the change to API users. It includes release notes, a migration guide, a deprecation notice, and a sunset timeline.
Release notes explain what changed. The migration guide explains how clients should update. The deprecation notice warns that old behavior will be removed later. The sunset timeline gives clients a clear deadline.
Stable API v1 should remain available during the agreed migration period. Monitoring adoption helps the team understand how many clients still depend on it. The old behavior should be retired only after communication, migration time, and the planned deprecation process are complete.
Practical Complexity & Trade-offs
The benefit of this design is safety. Stable API v1 stays available while the New API Change is tested and released slowly. The Version Router gives one place to control which behavior handles each request. Feature flags reduce risk because only selected clients see the change first. The downside is extra work. The team must support two behaviors, maintain compatibility rules, run several kinds of tests, manage traffic policies, and watch more metrics. Rollback is useful, but the rollback path must also be tested. Deprecation may take a long time because some clients update slowly. This process delays full delivery, but it lowers the chance of breaking a public API used by many clients.
Why Interviewers Ask This
Interviewers ask this question to test engineering judgment, not memorized API terms. They want to see whether you protect existing clients, separate compatible changes from versioned changes, and model the request and response path correctly. They also evaluate how testing, staging, feature flags, canary rollout, monitoring, rollback, deprecation, and client communication work together. A strong answer clearly explains the safety benefits and the added operational cost.
Interviewer may ask next
What would you do if the New API Change causes a high error rate during the canary rollout?
I would stop further exposure and shift canary traffic back to Stable API v1. Monitoring and Alerts already receives access logs and metrics from the API Gateway. It also receives service logs and metrics from both API implementations. When the error rate crosses the agreed limit, monitoring sends an alert to the Rollback Controller. The controller disables the rollout or changes the traffic split through Feature Flags and the Traffic Splitter. The Version Router then sends affected requests back to Stable API v1. The client request path does not change. Clients still call the API Gateway, and responses still return through the router and gateway. After traffic is stable, I would inspect the logs, reproduce the failure in the Staging or Sandbox Environment, fix the change, and rerun unit, contract, integration, and regression tests. The downside is a delayed release, but that is safer than exposing more public clients to faulty behavior.
How would you safely retire Stable API v1 after clients begin using the new behavior?
I would use the Client Communication and Deprecation Plan and remove v1 only after the announced migration period. First, I would publish release notes that explain the change. I would provide a migration guide showing what clients must update. I would then publish a deprecation notice and a clear sunset timeline. During that period, the Version Router would continue sending normal v1 traffic to Stable API v1. Flagged or versioned traffic would continue using the New API Change. Monitoring and Alerts would track adoption so the team can see how much traffic still depends on v1. I would contact important remaining clients before the sunset date and understand their blockers. The rollback path would remain available while the new behavior gains traffic. Stable API v1 would be retired only after the agreed timeline ends and remaining usage is understood. The downside is maintaining two behaviors for longer, but this avoids breaking clients without warning.
28. Design over-the-air delivery of car software updates.System DesignHardMicrosoft
i Question Details
A large car software update is split into packages for delivery. Design the system so vehicles can download and apply the update reliably when packages may be lost, duplicated, delayed, or received out of order.
Short Interview Answer (30-60 seconds)
At a high level, the system must receive every update package before changing the car software. The main challenge is unreliable delivery because packages can be lost, repeated, delayed, or received out of order. I would divide the design into release creation, reliable downloading, and safe activation. A manifest defines the expected package set. The vehicle retries only missing packages, removes duplicates, verifies integrity, and installs through staging. The trade-off is more vehicle-side storage and recovery logic.
Detailed Explanation
The system must deliver a large update without leaving the vehicle unusable. This is difficult because the network may lose, repeat, delay, or reorder packages. The diagram solves this in three parts. The Cloud OTA Platform prepares and offers the release. The vehicle downloads and verifies every required package. It then activates the update through staging, health checking, and rollback.
Useful Questions to Ask the Interviewer
Which user flows and system capabilities are required for the first version?
What traffic, data volume, latency, and availability targets should I design for?
Which consistency, security, geographic, and cost constraints matter most?
How to Explain It in an Interview
1. Prepare the release and define completeness
I would start with how the Cloud OTA Platform prepares an update. The Release Publisher creates a release through the OTA Update Service.
The OTA Update Service works with the Manifest Store and Package Store. The Package Store holds the split update packages. The Manifest Store holds the version, package identifiers, checksums, and expected order.
The manifest defines when the download is complete. The vehicle does not rely on package arrival order. It checks received identifiers against the expected package set.
The Device Registry provides the eligible vehicles. This lets the OTA Update Service offer the release only to the intended vehicles.
2. Check for an update and request packages
For the delivery path, the Vehicle Update Agent checks the OTA Update Service. It receives the update manifest and starts the download process.
The Download Manager asks for the missing packages. The Package Store delivers those packages. Some packages may arrive late or in the wrong order.
The Reorder Buffer holds these arrivals until they can be handled correctly. This keeps network arrival order separate from update package order.
3. Track progress and handle unreliable delivery
The Dedup + Missing Tracker records which package identifiers have arrived. It ignores duplicate packages because the same package may be delivered more than once.
It also finds gaps in the expected package set. The red retry path sends only missing package identifiers back to the Download Manager. Completed packages are not downloaded again.
This supports the diagram’s resume behavior. After a connection break, the vehicle continues with missing packages instead of restarting everything.
The Vehicle Update Agent reports download status and package ACKs. An ACK is a message confirming that a package was received.
4. Verify and assemble the full update
Each unique expected package moves to the Integrity Verifier. It checks the package checksum. A checksum is a small value used to detect damaged data.
Verified packages move to the Integrity Assembler. The Apply Coordinator waits until all packages are present. This prevents a partial update from being applied.
5. Activate safely and report the result
The Apply Coordinator applies the update to the Staging Partition. Staging is a separate area used before the new software becomes active.
The Health Check + Rollback step boots and validates the staged update. If validation succeeds, it becomes the Running Software. If activation fails, rollback keeps or restores the working software.
The apply result returns to the OTA Update Service. Campaign events, download status, package ACKs, and update results are recorded in Telemetry + Audit Logs.
The main trade-off is extra state, storage, and recovery logic inside the vehicle. That added complexity is useful because it avoids partial installs and repeated downloads.
Engineering Considerations / Design Trade-offs
The benefit is reliable delivery over an unreliable network. The manifest gives the vehicle a clear list of required packages. The missing tracker saves progress and retries only unfinished work. This reduces repeated downloads. The reorder buffer and duplicate checks add more memory and logic inside the vehicle. Integrity checks add processing before installation. Staging and rollback also need extra storage because the working software must remain safe during activation. We accept these costs because a broken car update is much worse than a slower update. The design chooses safety and recovery over the simplest download process.
Why Interviewers Ask This
Interviewers use this question to test how you handle unreliable delivery and safe software changes. They want to see whether you separate release creation, downloading, verification, and activation. They also expect clear handling of missing, repeated, delayed, and out-of-order packages. A strong answer explains why the manifest, saved progress, integrity checks, staging, health validation, and rollback are needed.
Interviewer may ask next
What changes if a vehicle loses connectivity for several days during the download?
I would keep the same design and rely on the saved package progress. The Dedup + Missing Tracker already knows which package identifiers arrived before the connection stopped.
When connectivity returns, the Vehicle Update Agent checks the update again. The Download Manager requests only the packages still missing from the expected manifest set. Packages already received are not downloaded again. Repeated deliveries are still ignored.
Every new package still passes through the Reorder Buffer and Integrity Verifier. The Apply Coordinator still waits until the complete verified set is present. This keeps the update correct even after a long interruption.
The vehicle reports updated download status and package ACKs when the connection returns. The main downside is local storage. The vehicle must keep partial packages and tracking state for a longer time. It also needs cleanup rules after the update finishes or is cancelled.
How should the system behave when every package arrives, but the new software fails its health check?
The vehicle should not download the whole update again. Package delivery already succeeded, and the Integrity Verifier confirmed the package data.
The Apply Coordinator has placed the update in the Staging Partition. The Health Check + Rollback step then boots and validates that staged software. When the check fails, the new software must not become the Running Software.
Rollback keeps or restores the previous working software. The vehicle sends the failed apply result to the OTA Update Service. The result is also recorded in Telemetry + Audit Logs with the related campaign events.
This keeps the vehicle usable even when the release contains a bad update. The main downside is extra storage and activation time. The vehicle must preserve a working version while testing the staged version. The rollback path also needs careful testing because it is the final safety step.
29. Design a multi-tenant to-do list service.System DesignMediumMicrosoft
i Question Details
Design the REST API and backend for users to create named lists and create, read, update, delete, order, and list tasks. Cover tenant isolation, authentication and authorization, a single-user burst of roughly 500 requests per second, caching and staleness, pagination, API versioning, service-to-service authentication, and read latency.
Short Interview Answer (30-60 seconds)
At a high level, this service lets each tenant manage named lists and ordered tasks safely. The main challenge is keeping reads fast while preventing users from accessing another tenant’s data. I would explain the design through request entry, writes, and reads. Requests pass through authentication, authorization, rate limits, and a versioned REST API. Writes update the Primary Database. Reads use the Read Cache first, then Read Replicas. The trade-off is that cached or replica data may briefly be old.
Detailed Explanation
The goal is to let users create named to-do lists and manage ordered tasks inside them. Users must create, read, update, delete, reorder, and list tasks. Each request must stay inside the correct tenant. The system must also handle a short burst of about 500 requests per second from one user. The design separates protected request entry, correct database writes, and fast cached or replica reads.
Useful Questions to Ask the Interviewer
Which user flows and system capabilities are required for the first version?
What traffic, data volume, latency, and availability targets should I design for?
Which consistency, security, geographic, and cost constraints matter most?
How to Explain It in an Interview
1. Explain the goal and the main idea
I would begin by saying that tenant isolation is the first rule. Every list and task belongs to a tenant. The user must also have permission to access that tenant’s data.
The service supports named lists and ordered tasks. Large task lists are returned in pages. Pagination means returning a small group of results with a page token instead of returning everything at once.
2. Explain how requests enter the system
The Client sends each request to the API Gateway. The gateway passes it through Authentication + Rate Limits. Authentication confirms the user’s identity. Rate limits protect the service from large bursts, including roughly 500 requests per second from one user.
The request then reaches the Versioned REST API. The diagram shows v1 and v2 support. This lets newer clients use updated behavior without immediately breaking older clients.
Internal calls use Service-to-Service Auth. This checks that trusted services are allowed to call the API, To-Do Service, Read Cache, and data layer.
3. Explain the write path
For create, update, delete, or reorder requests, the Versioned REST API sends the work to the To-Do Service. Tenant Resolver + Authorization finds the tenant and checks the user’s access.
List Service handles named-list operations. Task Service handles task operations. Ordering + Pagination manages task positions and page-based listing.
The write then goes to the Primary Database. This database stores the official list and task data. Example operations include creating a list, updating a task, and reordering tasks inside a list.
After the database write, the service invalidates or refreshes the Read Cache. Invalidation removes an old cached result. Refresh replaces it with newer data.
4. Explain the read path
GET requests first check the Read Cache. On a cache hit, the Versioned REST API receives the cached result quickly. This provides the lowest read latency for frequently requested lists and tasks.
If the cache does not contain the result, the request goes to the To-Do Service. Tenant Resolver + Authorization still checks access. Ordering + Pagination reads the requested page from Read Replicas.
Read Replicas are read-only copies of the Primary Database. The Primary Database sends changes to them through replication. They reduce read work on the main database.
5. Explain monitoring, limits, and trade-offs
Observability & Monitoring receives signals from the API Gateway, Versioned REST API, To-Do Service, and data layer. These signals help the team find errors, slow requests, and overloaded components.
The main trade-off is read freshness. The cache may briefly contain older data. Read Replicas may also be slightly behind the Primary Database. This small delay is called replication lag. The design accepts this risk because cache and replica reads are faster and reduce pressure on the main database.
Engineering Considerations / Design Trade-offs
The benefit is faster reads. The Read Cache can return popular lists and tasks without another database read. Read Replicas also reduce work on the Primary Database. The downside is that cached or replicated data may be slightly old after a write. The service reduces this problem by invalidating or refreshing the cache after updates. Rate limits protect the system during a large single-user burst. API versioning also protects older clients when the API changes. The downside is more maintenance because the team may need to support two API versions and investigate stale-read problems.
Why Interviewers Ask This
Interviewers ask this question to see whether you can divide a large problem into clear request, write, and read flows. They want to test how you protect tenant data and control user access. They also look for good choices around caching, pagination, read replicas, rate limits, API versioning, and internal security. Most importantly, they want to see whether you can explain the speed-versus-freshness trade-off clearly.
Interviewer may ask next
What would you change if users must always see their own latest task update immediately?
I would keep the same basic design, but change the read path immediately after a successful write. The affected parts are the To-Do Service, Primary Database, Read Cache, and Read Replicas.
The write must still complete in the Primary Database first. After that, the service should invalidate or refresh the matching cache entry. For the same user’s next read, the system should avoid a replica that may still be behind. It can read from the Primary Database or return the value confirmed by the completed write.
Other reads can continue using the normal Read Cache and Read Replicas path. This keeps most traffic fast while giving the writer a newer result.
Correctness comes from using the completed database write as the trusted result. The main downside is more traffic on the Primary Database. The service also needs a way to identify which recent reads require this stronger behavior.
What happens if the Read Cache becomes unavailable?
I would keep the same architecture and treat every cache request as a cache miss. The Versioned REST API would send the read to the To-Do Service instead of waiting for the cache.
Tenant Resolver + Authorization would still confirm the tenant and user access. Ordering + Pagination would then read the requested page from Read Replicas. Writes would continue going to the Primary Database.
The data remains correct because the Read Cache is only a speed layer. It is not the main data store. Observability & Monitoring should report the cache failure, higher replica traffic, and slower response times.
Rate limits become more important because more reads now reach the data layer. The service may also reduce traffic if the replicas become overloaded. The main downside is higher read latency and more pressure on Read Replicas until the cache returns.
30. Design an elevator system.System DesignMediumMicrosoft
i Question Details
Identify the requirements, classes, responsibilities, and public operations for an elevator system, and explain how the design follows encapsulation and the single-responsibility principle.
Short Interview Answer (30-60 seconds)
At a high level, this system accepts floor requests and moves the right elevator safely. The main challenge is keeping request handling separate from movement, door control, and display updates. I would explain it in three flows: hall calls, in-car selections, and elevator actions. The Hall Panel and Car Panel collect input. The Elevator System and Dispatcher choose an Elevator. That Elevator controls its Door, Motor, and Display. The trade-off is more classes, but each class becomes easier to understand, test, and change.
Detailed Explanation
The goal is to accept a passenger request, choose an Elevator, and move it to the requested floor. The hard part is keeping input, assignment, movement, door control, and status display separate. The diagram solves this with small classes that each own one clear job. I would explain the design by following a hall call, then an in-car selection, and finally the Elevator’s internal actions.
Useful Questions to Ask the Interviewer
Which user flows and system capabilities are required for the first version?
What traffic, data volume, latency, and availability targets should I design for?
Which consistency, security, geographic, and cost constraints matter most?
How to Explain It in an Interview
1. Explain the goal and the main idea
I would start by saying that the system has two input paths. A Hall Panel collects an up or down call. A Car Panel collects a destination after the passenger enters.
The Elevator System is the main entry point. It accepts requests, provides status through getStatus(), and manages Elevator objects. The Dispatcher handles the separate job of choosing the best Elevator.
2. Explain the hall-call flow
For a hall call, the Hall Panel uses callUp() or callDown(). It sends requestElevator(floor, direction) to the Elevator System.
The floor tells us where the passenger is. The direction tells us whether the passenger wants to go up or down.
The Elevator System submits the request to the Dispatcher. The Dispatcher reads a Request object carrying floor, direction, and type. It then uses assignElevator(request) to choose an Elevator.
This keeps request entry separate from assignment rules. Those rules can change without changing movement code.
3. Explain the in-car selection flow
After the passenger enters, the Car Panel collects the destination. Its selectFloor(floor) operation sends addDestination(floor) to the Elevator.
The Car Panel also exposes openDoor() and closeDoor(). These are passenger commands. The Elevator still owns the real action and decides when to call the Door.
This matters because the panel should never move the car itself. It only sends commands to the Elevator.
4. Explain how one Elevator works
The Elevator manages currentFloor, direction, and status. Other classes do not change these values directly.
It exposes addDestination(floor), moveUp(), moveDown(), stopAt(floor), openDoor(), and closeDoor(). These methods hide the internal movement state.
For movement, the Elevator calls startUp(), startDown(), or stop() on the Motor. For access, it calls open() or close() on the Door. For passenger feedback, it updates the Display through showFloor(floor) and showDirection(direction).
The Elevator is therefore the coordinator for one car. The Door, Motor, and Display each handle one smaller concern.
5. Explain encapsulation and single responsibility
Encapsulation means internal state stays behind public methods. A caller asks the Elevator to add a destination or move. It does not directly rewrite currentFloor, direction, or status.
Single responsibility means each class has one focused job. Panels collect input. The Dispatcher assigns elevators. The Elevator manages a trip. The Door controls access. The Motor drives movement. The Display shows status.
The benefit is easier testing and safer changes. The downside is more classes and more relationships to manage.
Engineering Considerations / Design Trade-offs
The benefit is clear ownership. Each class handles one kind of work. This makes changes safer because request logic, movement logic, door logic, and display logic stay separate. It also makes testing easier because each class can be checked alone. The downside is extra structure. A very small program could use fewer classes, but those classes would mix many jobs. The Dispatcher also adds one step before an Elevator is chosen. We accept this because assignment rules can change without changing the Elevator, Motor, Door, or Display. It also keeps future changes focused.
Why Interviewers Ask This
Interviewers ask this question to see how you break one real system into clear classes. They want to know whether each class owns the right job. They also check whether you can explain request flow, state ownership, public methods, and class relationships. A strong answer shows good object-oriented judgment, not just knowledge of class names.
Interviewer may ask next
How would the design change if the building had many elevators and several hall calls arrived together?
I would keep the same classes, but the Dispatcher would do more work. The Hall Panel would still send each hall request to the Elevator System. The Elevator System would still submit a Request to the Dispatcher.
The main change would be inside assignElevator(request). The Dispatcher would compare the available Elevator objects. It could use each Elevator’s currentFloor, direction, and status, because those values already belong to the Elevator.
Each Elevator would still control its own Motor, Door, and Display. This keeps car behavior separate from assignment logic. Several calls could be assigned to different elevators when that gives a better result.
Correctness stays clear because the Dispatcher remains the class that chooses an Elevator for each hall request. The main downside is a harder assignment rule. It must make good choices while many elevators are moving and new calls keep arriving.
What should happen if the Door cannot close when the Elevator is ready to move?
I would keep the same classes and make the Elevator refuse to move until the Door is closed. The affected classes are the Elevator, Door, and Motor.
The Elevator already coordinates openDoor(), closeDoor(), moveUp(), and moveDown(). It should complete the close-door action before calling Motor.startUp() or Motor.startDown(). If the Door cannot close, the Elevator should remain stopped at its currentFloor.
The Display can continue showing the current floor and direction. The Door still owns access control. The Motor still owns movement. The Elevator keeps correctness by enforcing one rule: movement starts only after the door-closing step succeeds.
The main downside is lower availability for that Elevator. It may stay unavailable until the door problem is fixed.
More questions load as you scroll
Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.