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.
11. How would you solve Sort Colors?CodingEasyMicrosoft
i Question Details
Sort an array containing only 0s, 1s, and 2s in place. Explain the pointer strategy and why it runs in linear time.
Short Interview Answer (30-60 seconds)
I would use the Dutch National Flag three-pointer approach. I keep low, mid, and high pointers. Values before low are 0s, values from low through mid - 1 are 1s, and values after high are 2s. I inspect nums[mid]. For 0, I swap left and move low and mid. For 1, I move mid. For 2, I swap right and move high only. This sorts in O(n) time with O(1) auxiliary space.
The input is an array that contains only 0s, 1s, and 2s. We need to change the same array so every 0 comes first, then every 1, then every 2. We should not create another array for the result. The main idea is to use three moving positions to separate finished values from values that still need work. Each step looks at one unfinished value and places it into the correct part. When no unfinished values remain, the same input array contains the required sorted result.
Useful Questions to Ask the Interviewer
Can I assume the array contains only 0s, 1s, and 2s?
Should I modify the input array in place instead of returning a new array?
How to Explain It in an Interview
1. Understand the input and required output
The example input is [2, 0, 2, 1, 1, 0]. We need to modify that same array into [0, 0, 1, 1, 2, 2]. We are sorting only three possible values. We do not need to preserve the original order of equal values because equal values are identical for this problem.
2. Choose the three-pointer algorithm
I use the Dutch National Flag algorithm. The array is divided into four regions. Positions from 0 through low - 1 contain finished 0s. Positions from low through mid - 1 contain finished 1s. Positions from mid through high are still unknown. Positions from high + 1 through the end contain finished 2s.
This invariant tells us what every part of the array means during the loop. We only need to classify the value at mid.
3. Initialize the state
For [2, 0, 2, 1, 1, 0], the indices are 0, 1, 2, 3, 4, 5. We start with low = 0, mid = 0, and high = 5. At this point, no value has been classified yet, so the whole range from mid through high is the unknown region.
The loop continues while mid <= high. When mid becomes greater than high, the unknown region is empty.
4. Walk through the example
Step 1: low = 0, mid = 0, high = 5. nums[mid] is 2. A 2 belongs on the right. Swap indices 0 and 5. The array becomes [0, 0, 2, 1, 1, 2]. Decrease high to 4. Keep mid at 0 because the value that was swapped into index 0 has not been classified yet.
Step 2: low = 0, mid = 0, high = 4. nums[mid] is now 0. A 0 belongs on the left. Swap indices 0 and 0. The array stays [0, 0, 2, 1, 1, 2]. Increase low to 1 and mid to 1.
Step 3: low = 1, mid = 1, high = 4. nums[mid] is 0. Swap indices 1 and 1. The array stays [0, 0, 2, 1, 1, 2]. Increase low to 2 and mid to 2.
Step 4: low = 2, mid = 2, high = 4. nums[mid] is 2. Swap indices 2 and 4. The array becomes [0, 0, 1, 1, 2, 2]. Decrease high to 3. Keep mid at 2 so the new value at index 2 can be checked.
Step 5: low = 2, mid = 2, high = 3. nums[mid] is 1. A 1 already belongs in the middle region. Increase only mid to 3. The array remains [0, 0, 1, 1, 2, 2].
Step 6: low = 2, mid = 3, high =
nums[mid] is 1 again. Increase mid to
The array remains [0, 0, 1, 1, 2, 2].
Now mid = 4 and high = 3. Since mid > high, the unknown region is empty and processing stops. The final result is [0, 0, 1, 1, 2, 2].
5. Explain why the result is correct
The invariant is preserved after every operation. Everything before low is a
Everything from low through mid - 1 is a
Everything after high is a
The range from mid through high contains values that still need classification. Each iteration shrinks that unknown region by one position. When mid passes high, every value has been classified, so the array is sorted correctly.
6. Explain the Java implementation
The Java method creates low, mid, and high. It loops while mid <= high. If nums[mid] is 0, it swaps with low and advances both low and mid. If nums[mid] is 1, it advances mid. If nums[mid] is 2, it swaps with high and decreases high without advancing mid. A small swap method exchanges two array elements.
7. Explain complexity and edge cases
The running time is O(n). Every loop iteration shrinks the unknown range [mid..high] by one position, so there are at most n iterations. The algorithm uses only low, mid, high, and one temporary swap value, so auxiliary space is O(1). Relevant edge cases include an empty array, one element, an already sorted array, and an array where every value is the same.
Key Insight / Why This Solution Works
The key idea is to partition the array into four regions while processing it. The invariant is: [0..low-1] contains only 0s, [low..mid-1] contains only 1s, [mid..high] is still unknown, and [high+1..n-1] contains only 2s. We inspect nums[mid]. A 0 is moved to the left, a 1 stays in the middle, and a 2 is moved to the right. Every operation shrinks the unknown region by one position. This avoids a separate result array and gives an in-place linear-time solution.
Code
import java.util.Arrays;
publicclassMain {
publicstaticvoidsortColors(int[] nums) {
// low is the next position where a 0 should be placed.// mid is the current position that still needs classification.// high is the next position where a 2 should be placed.intlow=0;
intmid=0;
inthigh= nums.length - 1;
// Continue while the unknown region [mid..high] is not empty.while (mid <= high) {
if (nums[mid] == 0) {
// A 0 belongs in the left region.// Move it to low, then advance both finished boundaries.
swap(nums, low, mid);
low++;
mid++;
} elseif (nums[mid] == 1) {
// A 1 already belongs in the middle region.// Only mid moves forward.
mid++;
} else {
// A 2 belongs in the right region.// Move it to high and shrink the unknown region from the right.
swap(nums, mid, high);
high--;
// Do not increment mid here.// The value swapped in from the right has not been classified yet.
}
}
}
privatestaticvoidswap(int[] nums, int i, int j) {
// Exchange two values in place without creating another array.inttemp= nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
publicstaticvoidmain(String[] args) {
// Use the exact example shown in the approved diagram.int[] nums = { 2, 0, 2, 1, 1, 0 };
// Sort the original array in place.
sortColors(nums);
// Expected output: [0, 0, 1, 1, 2, 2]
System.out.println(Arrays.toString(nums));
}
}
Time & Space Complexity
Time complexity is O(n). The unknown region [mid..high] becomes smaller by one position on every loop iteration, so there are at most n iterations. Swaps are constant-time operations, so the total work stays linear. Auxiliary space is O(1). Auxiliary space means extra memory used by the algorithm. We only keep three pointer variables and one temporary value for swapping, so the extra memory does not grow with the input size.
Where it is used
This three-way partition pattern is useful when data belongs to three groups and we want to separate those groups in place. A closely related idea is used in three-way partitioning for quicksort when many duplicate values are present. It can also be useful when records need to be divided into low, middle, and high categories without allocating another full array.
Why Interviewers Ask This
This problem checks whether you can recognize a three-way partitioning pattern instead of using a general sorting algorithm. The interviewer can see whether you maintain a precise pointer invariant, move each pointer for the correct reason, and handle the important 2-swap case correctly. It also tests whether you can mutate an array safely in Java and explain why the solution uses O(n) time and O(1) auxiliary space.
Common interview mistakes
A common mistake is incrementing mid after swapping a 2 with nums[high]. The new value at mid has not been checked, so mid must stay in place. Another mistake is moving only mid after finding a 0. Both low and mid must advance after that swap. Candidates may also use the wrong loop condition and stop before processing high. The correct condition is mid <= high. Another mistake is describing the runtime as more than O(n) because swaps occur. Each iteration still shrinks the unknown region by one position. Finally, the four pointer regions must stay consistent with the invariant throughout the loop.
Interview tip
State the four-region invariant before writing the loop. Then explain each branch by saying where the current value belongs and which boundary must move. Pay special attention to the 2 case and say clearly that mid does not advance because the swapped-in value still needs to be classified.
Interviewer may ask next
Why do we not increment mid after nums[mid] is 2?
We swap nums[mid] with nums[high], so a new value arrives at mid from the unknown region. We do not yet know whether that value is 0, 1, or 2. We therefore decrease high but keep mid in the same position and classify the new value on the next iteration. This preserves the invariant. The complexity remains O(n) time and O(1) auxiliary space.
What happens if the array is already sorted or all values are the same?
The same algorithm still works. For an already sorted array, 0s expand the left region, 1s move mid forward, and 2s are handled at the right boundary until the unknown region disappears. If every value is the same, the matching branch repeatedly shrinks the unknown region. No special case is required. The time remains O(n), and the auxiliary space remains O(1).
12. How would you build a time-based key-value store?CodingMediumMicrosoft
i Question Details
Design and implement a data structure that stores values at timestamps and returns the most recent value at or before a given time.
Short Interview Answer (30-60 seconds)
I would store each key in a hash map, and for each key I would keep a TreeMap of timestamp to value. On set, I write the value at its timestamp. On get, I use floorEntry to find the largest timestamp that is not after the query time, then return that value. I process each lookup in order, so the answer is correct and fast. The time is O(log n_k) per operation, and the extra space is O(N).
This problem asks me to remember a value for each key at different times and then answer with the newest value that is not later than the time I ask for. I keep the writes for each key in time order. That lets me find the right answer without checking every old value. When I set a value, I store it at its timestamp. When I get a value, I look for the largest timestamp that is still at or before the query time, and I return that value or null if there is none.
Useful Questions to Ask the Interviewer
If the same key is written twice at the same timestamp, should the later write replace the earlier one?
Should I return null when no timestamp exists at or before the query time?
How to Explain It in an Interview
1. Understand the input and required output
The input has a key, a value, and a timestamp for set. The input for get has a key and a timestamp. The output is the value with the largest timestamp that is less than or equal to the query time. If the key does not exist or there is no earlier timestamp, the answer is null.
2. Choose the algorithm and data structure
I use a HashMap from key to TreeMap. The HashMap finds the right key quickly. The TreeMap keeps that key’s timestamps in sorted order. This is important because the query asks for the latest time that is still valid, not just any stored time.
3. Initialize the state
At the start, the store is empty. For each new key, I create one TreeMap. The invariant is simple. For each key, the TreeMap stores timestamp to value pairs in sorted timestamp order. Only past writes for that key are inside that TreeMap.
4. Walk through the example
The diagram uses the same example everywhere. For user1, we store A at 1, B at 5, and C at 10. Then we overwrite the value at timestamp 5 with D. After that, the stored history for user1 is {1 → A, 5 → D, 10 → C}. So get(user1, 6) returns D because 5 is the largest timestamp that is not after 6. get(user1, 1) returns A. get(user1, 10) returns C. get(user1, 11) also returns C.
5. Explain why the result is correct
The TreeMap keeps timestamps sorted. That means floorEntry(t) gives the entry with the largest timestamp that is less than or equal to t. That is exactly the rule for this problem. Because each key has its own TreeMap, the query never mixes values from different keys.
6. Explain the Java implementation
The set method uses computeIfAbsent to create the TreeMap when a key appears for the first time. Then it puts the timestamp and value into that TreeMap. The get method first reads the TreeMap for the key. If the key is missing, it returns null. Otherwise it calls floorEntry(timestamp). If that is null, it returns null. If it finds an entry, it returns the stored value.
7. Explain complexity and edge cases
Each set and get uses the TreeMap for one key, so the time is O(log n_k), where n_k is the number of timestamps stored for that key. The extra memory is O(N), where N is the total number of stored timestamp-value pairs. Important edge cases are a missing key, a query time before the first stored timestamp, duplicate writes at the same timestamp, and many timestamps for one key.
Key Insight / Why This Solution Works
The key idea is to keep the data sorted by time for each key. The HashMap chooses the right key bucket, and the TreeMap keeps that key’s writes in timestamp order. The invariant is that each TreeMap contains only writes for one key, sorted by timestamp. Then floorEntry(t) returns the newest value that is still valid for time t. This matches the problem rule exactly, so we do not need to scan older values one by one.
Code
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
publicclassMain {
staticclassTimeMap {
// key -> (timestamp -> value)privatefinal Map<String, TreeMap<Integer, String>> store;
publicTimeMap() {
// Start with an empty store.this.store = newHashMap<>();
}
publicvoidset(String key, String value, int timestamp) {
// Create the ordered map for this key if it does not exist yet.// TreeMap keeps timestamps sorted for this key.
store
.computeIfAbsent(key, k -> newTreeMap<>())
// If the same timestamp is written again, the new value replaces the old one.
.put(timestamp, value);
}
public String get(String key, int timestamp) {
// If the key does not exist, there is no answer.
TreeMap<Integer, String> timeMap = store.get(key);
if (timeMap == null) {
returnnull;
}
// floorEntry gives the entry with the largest timestamp <= query time.
Map.Entry<Integer, String> entry = timeMap.floorEntry(timestamp);
if (entry == null) {
returnnull;
}
return entry.getValue();
}
}
publicstaticvoidmain(String[] args) {
TimeMaptm=newTimeMap();
// Exact example style shown in the diagram.
tm.set("user1", "A", 1);
tm.set("user1", "B", 5);
tm.set("user1", "C", 10);
tm.set("user1", "D", 5); // overwrite timestamp 5// Print the same kind of results shown in the diagram.
System.out.println(tm.get("user1", 0)); // null
System.out.println(tm.get("user1", 1)); // A
System.out.println(tm.get("user1", 6)); // D
System.out.println(tm.get("user1", 10)); // C
System.out.println(tm.get("user1", 11)); // C
System.out.println(tm.get("user1", 5)); // D
}
}
Time & Space Complexity
Each operation works on one key. The HashMap finds that key quickly, and the TreeMap keeps the timestamps in order. TreeMap put and floorEntry both take O(log n_k) time, where n_k is the number of timestamps for that key. So set and get are O(log n_k) time each. The extra memory is O(N), where N is the total number of stored timestamp-value pairs across all keys.
Where it is used
This pattern is useful for version history, audit logs, cache snapshots, feature flag history, and any system that must answer: “What was the value as of this time?” It is also useful when you need time travel queries, because each key keeps its own ordered history.
Why Interviewers Ask This
The interviewer wants to see whether I can model versioned data correctly, choose an ordered data structure, and explain why a plain hash lookup is not enough. They also check whether I preserve the time order, handle missing keys, handle repeated writes, and use correct Java collections. This question shows how well I connect the problem rule to the data structure and the query operation.
Common interview mistakes
A common mistake is to use only a HashMap and lose the timestamp order. Another mistake is to return the first time that is close enough, instead of the latest time that is not after the query time. A third mistake is to use ceilingEntry instead of floorEntry. That gives a future value, which is wrong here. People also forget to return null when the key is missing or when no earlier timestamp exists. Finally, some candidates forget that a later write at the same timestamp must replace the earlier value.
Interview tip
Say the invariant out loud: each key points to a sorted TreeMap of its past writes. Then say why floorEntry matches the phrase “latest at or before the query time.”
Interviewer may ask next
How would you support deleting a value at a specific timestamp?
I would remove that timestamp from the TreeMap for the key. If the TreeMap becomes empty, I would remove the key from the HashMap too. The time stays O(log n_k) because TreeMap removal is logarithmic.
How would you return every value up to a timestamp instead of only the latest one?
I would use the ordered TreeMap to iterate through the entries up to that time, for example with headMap(timestamp, true). The correctness stays the same, but the time becomes proportional to the number of values I return.
13. How would you find all balanced k values in a permutation?CodingHardMicrosoft
i Question Details
Given a permutation, determine for each k whether a contiguous subarray contains exactly the set {1..k}. Explain an efficient approach and its complexity.
Short Interview Answer (30-60 seconds)
I would first build a position array so pos[x] tells me where value x appears in the permutation. Then I process k from 1 to n and keep the minimum and maximum positions seen for values 1 through k. Those values are contiguous exactly when maxPos - minPos + 1 equals k. If that condition is true, I record k. This takes O(n) time and O(n) auxiliary space.
The input is a permutation containing each value from 1 to n exactly once. For every k, we want to know whether the values 1 through k appear together in one continuous part of the array, in any order. We return every k for which this is true. The key idea is to store where each value appears. As we add values in the order 1, 2, 3, and so on, we track the leftmost and rightmost positions. If those positions cover exactly k slots, the values must be consecutive.
Useful Questions to Ask the Interviewer
Can I assume the input is always a valid permutation of values 1 through n?
Should I return the balanced k values themselves, as shown in the example?
How to Explain It in an Interview
1. Understand the input and required output
The input is a permutation. This means every value from 1 to n appears exactly once. For each k, we check whether one contiguous subarray contains exactly the set {1..k}. The values can appear in any order inside that subarray. We return every k that satisfies this condition.
For the example p = [4, 1, 3, 2, 5], the balanced k values are [1, 3, 4, 5]. The corresponding answers for k = 1 through 5 are [true, false, true, true, true].
2. Build the position array
I build pos[value] = position of that value. I use 1-based positions to match the walkthrough.
For p = [4, 1, 3, 2, 5]:
pos[1] = 2
pos[2] = 4
pos[3] = 3
pos[4] = 1
pos[5] = 5
Now I can find the position of each next value in constant time using an array lookup.
3. Maintain the current position span
I initialize minPos to positive infinity and maxPos to negative infinity. Then I process k from 1 to n.
After processing k, minPos and maxPos describe the smallest and largest positions occupied by the values {1..k}. This is the main invariant.
For each k, I read pos[k]. I update minPos and maxPos. Then I calculate:
span = maxPos - minPos + 1
If span equals k, the k distinct positions must fill every position in that interval. Therefore the values 1 through k form one contiguous subarray.
4. Walk through the example
For k = 1, pos[1] = 2. The state changes from no positions to minPos = 2 and maxPos = 2. The span is 2 - 2 + 1 = 1. Since 1 equals k, record 1. The result is [1]. The matching subarray is p[2..2] = [1].
For k = 2, pos[2] = 4. Before this step, the span is [2,2]. After adding position 4, minPos = 2 and maxPos = 4. The span is 4 - 2 + 1 = 3. Since 3 is not equal to 2, do not record 2. The result stays [1]. Positions 2 and 4 leave a gap at position 3, so values {1,2} are not contiguous.
For k = 3, pos[3] = 3. minPos stays 2 and maxPos stays 4. The span is 4 - 2 + 1 = 3. Since 3 equals k, record 3. The result becomes [1,3]. The subarray p[2..4] = [1,3,2] contains exactly {1,2,3}.
For k = 4, pos[4] = 1. minPos becomes 1 and maxPos remains 4. The span is 4 - 1 + 1 = 4. Since 4 equals k, record 4. The result becomes [1,3,4]. The subarray p[1..4] = [4,1,3,2] contains exactly {1,2,3,4}.
For k = 5, pos[5] = 5. minPos remains 1 and maxPos becomes 5. The span is 5 - 1 + 1 = 5. Since 5 equals k, record 5. The final result is [1,3,4,5].
5. Explain why the result is correct
After processing k, minPos and maxPos are the minimum and maximum positions of the values 1 through k. Because the input is a permutation, those k values occupy k different positions.
If maxPos - minPos + 1 equals k, the interval contains exactly k positions. Since we already have k distinct tracked positions inside it, there can be no gaps. The interval therefore contains exactly the values {1..k}.
If the span is larger than k, at least one position inside that interval is not occupied by one of the tracked values. That position contains another value from the permutation, so {1..k} does not form one contiguous subarray.
6. Explain the Java implementation
The Java code first builds the pos array in one loop. It then creates the result list and initializes minPos and maxPos. The second loop processes k from 1 through n. Each iteration updates the current minimum and maximum position. It computes the span and adds k to the result only when the span is exactly k. After all k values are processed, it returns the result list.
7. Explain complexity and edge cases
Building the position array takes O(n) time. Processing k from 1 through n also takes O(n) time. Therefore the total time is O(n).
The position array uses O(n) auxiliary space. The returned result list can also contain up to n values.
For any valid permutation, k = 1 is always balanced because one value occupies one position. Also, k = n is always balanced because all n values occupy the entire permutation. The reasoning depends on the input being a true permutation with unique values.
Key Insight / Why This Solution Works
The key insight is to change the problem from checking subarrays to checking positions. Store pos[x], the position where value x appears. Then add values in increasing order: 1, 2, 3, and so on. Keep only the smallest and largest positions seen so far. The invariant is that after processing k, the interval [minPos, maxPos] covers every position belonging to values {1..k}. Since these are k distinct positions, they are contiguous exactly when the interval length maxPos - minPos + 1 is also k. If the interval is longer, at least one position in the interval belongs to a value larger than k, so k is not balanced.
Code
import java.util.ArrayList;
import java.util.List;
publicclassMain {
publicstatic List<Integer> balancedKValues(int[] perm) {
intn= perm.length;
// pos[value] stores the 1-based position of that value.// The input is a permutation, so every value from 1 to n appears once.int[] pos = newint[n + 1];
// Build the value-to-position mapping used by the main loop.for (inti=0; i < n; i++) {
pos[perm[i]] = i + 1;
}
// Store every k whose values 1..k occupy one contiguous block.
List<Integer> result = newArrayList<>();
// Track the smallest and largest positions occupied by values 1..k.intminPos= Integer.MAX_VALUE;
intmaxPos= Integer.MIN_VALUE;
// Process values in increasing order so the tracked set is exactly {1..k}.for (intk=1; k <= n; k++) {
intcurrentPos= pos[k];
// Expand the covered interval to include the position of value k.
minPos = Math.min(minPos, currentPos);
maxPos = Math.max(maxPos, currentPos);
// The k tracked values occupy k distinct positions.// A span of length k means those positions are consecutive with no gaps.if (maxPos - minPos + 1 == k) {
result.add(k);
}
}
// Return all balanced k values in increasing k order.return result;
}
publicstaticvoidmain(String[] args) {
// Run the exact example used in the approved diagram.int[] perm = { 4, 1, 3, 2, 5 };
// Expected output: [1, 3, 4, 5]
List<Integer> balanced = balancedKValues(perm);
System.out.println(balanced);
}
}
Time & Space Complexity
Building the position array takes O(n) time because every permutation element is processed once. The second loop processes every k from 1 through n once, so it also takes O(n) time. The total time is O(n). The pos array has n + 1 entries, so it uses O(n) auxiliary space. The result list can contain up to n balanced k values. Array lookup, minimum, maximum, and arithmetic operations are constant-time operations.
Where it is used
This pattern is useful when items have unique ranks such as 1 through n and we need to know when the first k ranked items occupy one continuous interval. The same position-mapping and expanding-span idea can help with permutation analysis, ordering checks, and problems that ask whether a growing set of ranked values forms one contiguous block.
Why Interviewers Ask This
This problem tests whether you can turn a subarray condition into a simpler position-based condition. The interviewer is looking for recognition that a permutation gives unique positions, careful maintenance of a running minimum and maximum, and a clear invariant. It also tests whether you can prove why a span of exactly k positions means the values 1 through k are contiguous, implement the idea correctly in Java, and give the correct O(n) time and O(n) auxiliary space analysis.
Common interview mistakes
A common mistake is to track the values themselves instead of their positions. The condition is about whether their positions form one continuous interval. Another mistake is forgetting the +1 in maxPos - minPos + 1, which gives the inclusive span length. Candidates may also reset minPos and maxPos for every k instead of carrying the state forward as k grows. Another mistake is recording k when the span is only less than or equal to k instead of exactly equal to k. Finally, claiming O(1) extra space is incorrect because the position array grows with n.
Interview tip
State the invariant early: after processing k, minPos and maxPos bound the positions of exactly the values 1 through k. Then the test maxPos - minPos + 1 == k becomes easy to justify and the rest of the implementation follows directly.
Interviewer may ask next
Can the auxiliary space be reduced below O(n)?
Not with this exact processing order unless the positions of values 1 through n are available another way. To process k in increasing value order, we need pos[k] quickly even though the permutation is stored in position order. The position array provides O(1) access per k and uses O(n) space. Without that stored mapping, repeatedly searching the permutation can increase the total time to O(n^2).
What changes if duplicate values are allowed instead of a permutation?
The current proof no longer works directly because it depends on values 1 through k appearing exactly once and therefore occupying exactly k distinct positions. With duplicates, one required value may appear more than once or another may be missing. We would need occurrence or frequency information and a different condition for deciding whether a subarray contains exactly the required set. The position-array plus min/max method shown here is specifically based on the permutation guarantee.
14. How would you solve Word Ladder II?CodingHardMicrosoft
i Question Details
Find all shortest transformation sequences between two words using one-letter transformations. Explain the BFS/graph approach and how you avoid duplicates.
Short Interview Answer (30-60 seconds)
I would use BFS to find the shortest transformation level, then DFS to rebuild every shortest sequence. The BFS queue processes words level by level. I keep a remaining-word HashSet and a parents map from each child to all parents that reach it at the same shortest level. I remove discovered words only after the whole level finishes, so valid shortest parents are not lost. The diagram's expected time is O(N * L * 26 + outputSize), with O(N + outputSize) auxiliary space.
We need to change beginWord into endWord by changing exactly one letter at each step. Every transformed word after the start must appear in wordList. We must return every sequence that uses the minimum number of changes. For the example, "hit" reaches "cog" in four transformations through two different shortest paths. BFS fits because it explores shorter paths before longer paths. We also keep each shortest previous word so that all minimum-length sequences can be rebuilt after BFS.
Useful Questions to Ask the Interviewer
Should I return an empty list when endWord is not present in wordList?
Can the shortest sequences be returned in any order?
Should all parents that reach the same word at the same shortest level be preserved?
How to Explain It in an Interview
1. Understand the input and required output
The input is beginWord, endWord, and wordList. In the approved example, beginWord is "hit", endWord is "cog", and wordList is ["hot", "dot", "dog", "lot", "log", "cog"]. Each move changes exactly one character. We must return all shortest transformation sequences. One valid result order is [["hit", "hot", "dot", "dog", "cog"], ["hit", "hot", "lot", "log", "cog"]]. The order of those two returned ladders may vary.
2. Choose BFS and a parent graph
I use BFS because every transformation has the same cost of one step. BFS processes words one distance level at a time. The queue therefore discovers minimum distances before longer distances. I use a HashSet called remaining for words that can still be discovered at a new shortest level. I also use a parents map with the direction child -> list of shortest parents. A child may have several parents when different words reach it during the same BFS level.
The central invariant is: BFS visits levels in increasing transformation distance, so the first level that reaches a word gives that word its shortest distance. The parents map keeps all predecessors that reach that word at that same shortest distance.
3. Initialize the state
The initial queue is [hit]. The initial remaining set is {hot, dot, dog, lot, log, cog}. The parents map is empty. found is false. The word length L is 3. The code first puts wordList into a HashSet. If endWord is missing from that set, it immediately returns an empty result because no valid ladder can end at endWord.
4. Walk through the exact example
At level 0, the queue is [hit]. The valid next word is hot. We record hot <- hit and put hot into the next BFS level. Its shortest distance is 1.
At level 1, the queue is [hot]. We discover dot and lot. We record dot <- hot and lot <- hot. The next queue is [dot, lot]. Their shortest distance is 2.
At level 2, the queue is [dot, lot]. From dot we discover dog, so we record dog <- dot. From lot we discover log, so we record log <- lot. The next queue is [dog, log]. Their shortest distance is 3.
At level 3, the queue is [dog, log]. Dog reaches cog, so we record cog <- dog and set found to true. We do not stop in the middle of this level. We continue processing the same level. Log also reaches cog, so we record cog <- log. After the whole level finishes, BFS stops. The shortest distance to cog is 4 transformations, or 5 words in each ladder.
The final parent links used by the example are hot <- hit, dot <- hot, lot <- hot, dog <- dot, log <- lot, cog <- dog, and cog <- log.
5. Explain why level-based removal matters
A newly discovered word is not removed from remaining immediately. Instead, it is also stored in nextLevel. Other words in the current BFS level may still reach that same child. This is how cog can keep both dog and log as shortest parents. After the entire level finishes, nextLevel is removed from remaining. That prevents a deeper level from adding a longer path to the same word.
6. Reconstruct every shortest ladder
After BFS stops, DFS starts at endWord and follows the parents map backward. From cog, one branch follows dog -> dot -> hot -> hit. The other follows log -> lot -> hot -> hit. The path is being built backward. When DFS reaches beginWord, it copies that path, reverses the copy, and adds the forward ladder to the result. After each recursive branch returns, the last parent is removed from the current path. This backtracking step restores the path before the next branch is explored.
7. Explain correctness, complexity, and edge cases
BFS processes states in increasing distance order. Therefore the first BFS level that reaches a word gives its minimum transformation distance. Finishing the whole level before removing its discovered words preserves every shortest parent. Once the first level containing endWord is complete, any deeper path would be longer and is ignored. DFS then follows exactly those shortest-parent links, so it returns all shortest ladders.
The diagram reports expected time O(N * L * 26 + outputSize), where N is the number of words and L is the word length. HashSet and HashMap operations are O(1) on average. The diagram reports O(N + outputSize) auxiliary space for the BFS structures, parent information, reconstruction state, and returned ladders. Relevant edge cases are endWord missing from wordList, duplicate words being collapsed by the set, several shortest parents reaching one child in the same level, and preventing longer paths after endWord's first level is found.
Key Insight / Why This Solution Works
The key insight is to use BFS only to build the shortest-path structure, rather than storing full transformation sequences in the BFS queue. BFS processes the unweighted word graph level by level. The invariant is that the first level that reaches a word gives its shortest distance. The parents map stores child -> every predecessor that reaches that child at the same shortest level. Words discovered during a level stay available through nextLevel until that whole level ends. This preserves multiple shortest parents such as cog <- dog and cog <- log. After the first level containing endWord finishes, DFS follows those parent links backward to generate every shortest ladder.
Code
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
publicclassMain {
publicstaticvoidmain(String[] args) {
// Run the exact example used in the approved diagram.StringbeginWord="hit";
StringendWord="cog";
List<String> wordList = List.of("hot", "dot", "dog", "lot", "log", "cog");
Solutionsolution=newSolution();
List<List<String>> result = solution.findLadders(beginWord, endWord, wordList);
// One valid output order is:// [[hit, hot, dot, dog, cog], [hit, hot, lot, log, cog]]// The order of the shortest ladders may vary.
System.out.println(result);
}
staticclassSolution {
public List<List<String>> findLadders(
String beginWord,
String endWord,
List<String> wordList
) {
// Store dictionary words in a set for average O(1)// membership checks. Duplicate input words collapse naturally.
Set<String> dict = newHashSet<>(wordList);
List<List<String>> result = newArrayList<>();
// A valid sequence cannot finish at endWord when endWord// is not present in the supplied word list.if (!dict.contains(endWord)) {
return result;
}
// Map each child word to every parent that reaches it// at the child's shortest BFS distance.
Map<String, List<String>> parents = newHashMap<>();
// BFS starts from beginWord and processes one distance level// at a time.
Queue<String> queue = newArrayDeque<>();
queue.offer(beginWord);
// remaining contains words that have not been finalized// at a shorter BFS level.
Set<String> remaining = newHashSet<>(dict);
remaining.remove(beginWord);
// found is set when the current level reaches endWord.// We still finish that level so endWord can keep every// shortest parent from the same distance.booleanfound=false;
while (!queue.isEmpty() && !found) {
// Freeze the current level size so newly queued children// are not processed until the next BFS level.intsize= queue.size();
// Hold all words discovered in this level. They are removed// from remaining only after this whole level is complete.
Set<String> nextLevel = newHashSet<>();
for (inti=0; i < size; i++) {
Stringword= queue.poll();
char[] chars = word.toCharArray();
// Generate every one-letter mutation of this word.for (intpos=0; pos < chars.length; pos++) {
charoriginal= chars[pos];
for (charch='a'; ch <= 'z'; ch++) {
// Replacing a character with itself makes no move.if (ch == original) {
continue;
}
chars[pos] = ch;
Stringnext=newString(chars);
// Ignore a word that was finalized at an earlier// level and was not discovered in this level.if (!remaining.contains(next) && !nextLevel.contains(next)) {
continue;
}
// Queue a next-level child only on its first// discovery in this level. A later same-level// parent may still be recorded below.if (remaining.contains(next)) {
if (nextLevel.add(next)) {
queue.offer(next);
}
}
// Keep every parent that reaches this child// during the same shortest BFS level.
parents.computeIfAbsent(next, key -> newArrayList<>()).add(word);
// Mark that this level reaches endWord, but do not// stop inside the level because another shortest// parent may still reach endWord.if (next.equals(endWord)) {
found = true;
}
}
// Restore this position before trying mutations at// the next character position.
chars[pos] = original;
}
}
// Finalize all words discovered at this shortest level.// Later, deeper levels cannot add longer parent links to them.
remaining.removeAll(nextLevel);
}
if (found) {
// Start reconstruction at endWord and walk through// shortest-parent links toward beginWord.
LinkedList<String> path = newLinkedList<>();
path.add(endWord);
buildPaths(endWord, beginWord, parents, path, result);
}
return result;
}
privatevoidbuildPaths(
String word,
String beginWord,
Map<String, List<String>> parents,
LinkedList<String> path,
List<List<String>> result
) {
// Reaching beginWord completes one shortest backward path.if (word.equals(beginWord)) {
List<String> ladder = newArrayList<>(path);
// path was built from endWord toward beginWord.// Reverse the copy so the returned ladder is forward.
Collections.reverse(ladder);
result.add(ladder);
return;
}
// Explore every shortest parent of the current word.for (String parent : parents.getOrDefault(word, Collections.emptyList())) {
// Choose this parent for the current reconstruction branch.
path.addLast(parent);
// Continue following shortest-parent links backward.
buildPaths(parent, beginWord, parents, path, result);
// Restore the path before exploring the next parent branch.
path.removeLast();
}
}
}
}
Time & Space Complexity
Let N be the number of words and L be the length of each word. The approved diagram uses expected time O(N * L * 26 + outputSize). For each processed word, the algorithm tries each of its L character positions and each of the 26 lowercase letters. HashSet and HashMap lookup and insertion are O(1) on average, so this is an expected-time statement rather than a guaranteed worst-case hashing bound. outputSize is the work needed to produce the returned ladders. The diagram gives O(N + outputSize) auxiliary space for the remaining set, BFS queue, parents information, DFS reconstruction state, and returned results.
Where it is used
This pattern is useful when a program needs every minimum-step path through an unweighted state space. Examples include puzzle-state transitions, small configuration changes, command sequences, and workflow transitions where several different shortest paths may reach the same state. The general pattern is BFS to discover shortest levels, a parent graph to remember all minimum-distance predecessors, and DFS or backtracking to reconstruct the actual shortest paths.
Why Interviewers Ask This
This problem tests whether you can combine shortest-path traversal with path reconstruction. The interviewer wants to see that you recognize BFS for minimum distance in an unweighted graph, but also understand why ordinary visited handling can lose valid shortest paths. It tests level-order reasoning, duplicate control, storing multiple parents, DFS backtracking, Java collection choices, stopping at the correct time, handling edge cases, and explaining expected hash-based complexity without confusing it with a guaranteed worst-case bound.
Common interview mistakes
One common mistake is removing a discovered word from remaining immediately. That can lose another valid parent that reaches the same child during the same BFS level. Another mistake is stopping as soon as dog first reaches cog. The rest of that level must still run so log can also become a shortest parent of cog. Candidates may also enqueue the same child more than once in one level, allow deeper levels to add longer parent links after a shorter distance is known, or forget path.removeLast() when DFS backtracks.
Interview tip
Explain the visited timing before writing the DFS. Use cog as the concrete example: dog finds cog first, but cog stays available to the current level so log can also be recorded as a shortest parent. Then remove all next-level words only after that level finishes.
Interviewer may ask next
Why do we remove next-level words from remaining only after the whole BFS level finishes?
Different words in the same BFS level can reach the same child with the same minimum distance. In the example, dog and log both reach cog. If cog were removed as soon as dog found it, log could be blocked and one valid shortest ladder would be lost. Keeping nextLevel separate lets cog collect both parents. After the level ends, removing nextLevel prevents deeper, longer paths. The diagram's expected time remains O(N * L * 26 + outputSize), with O(N + outputSize) auxiliary space.
What happens when endWord is not present in wordList?
The method returns an empty list before BFS starts. Every transformed word after beginWord must come from wordList, so a missing endWord cannot be the final word of a valid sequence. Building the HashSet takes expected O(N) set insertions, the membership check is O(1) on average, and the set uses O(N) extra space. No BFS traversal or DFS reconstruction is performed in this case.
15. How would you design a low-latency product price update API?System DesignMediumMicrosoft
i Question Details
Design an API and serving system for product prices where reads are much more frequent than writes. Cover versioned price records, cache invalidation, rollback, consistency, bulk reads, and low-latency launch-page reads.
Short Interview Answer (30-60 seconds)
At a high level, this is a read-heavy price service. The hard part is keeping reads very fast while price changes stay correct and easy to undo. I would explain it in three parts: the write path that creates a new version, the read path that serves cache first, and the background work that updates cache and audit records. The trade-off is simple: we get low-latency reads, but some copies may show a slightly old price for a short time.
Detailed Explanation
This question asks for a service that stores product prices and serves them very quickly. Most traffic is reading prices, so the fast path matters most. Writes still need to be safe, versioned, and easy to roll back. The diagram shows a split between the read path, the write path, and background work that keeps cache and history in sync. I would explain the answer in that order, because it makes the trade-off clear: very fast reads, but a short delay before every copy shows the newest price.
Useful Questions to Ask the Interviewer
How fresh must a price be on the launch page, and can it be a little old for a short time?
How large can one bulk read request be, and do all clients need the same latency?
How to Explain It in an Interview
1. Explain the goal and the main idea
At a high level, this is a read-heavy price system. The main goal is to serve product prices fast without losing correctness. The main database keeps the official price records. The cache keeps hot price data close to the service, so most reads avoid the database.
2. Explain the write path
For the write path, the request first passes edge protection and the API gateway. Those parts handle login, access checks, validation, and rate limits. Then the Price Write Service checks the new price and saves a new version in the price_records table. The database write comes first, because the database is the source of truth. After that commit, the service publishes a PriceUpdated event.
3. Explain the read path
For the read path, the request reaches the Price Read Service. It checks the distributed in-memory cache first. On a hit, it returns the current price right away as JSON. On a miss, it reads from the main database and then fills the cache again. For launch-page traffic, hot products can be pre-warmed in cache, and public snapshots can use a short-TTL CDN when a little extra staleness is acceptable.
4. Explain bulk reads and background work
Bulk reads use GET /v1/prices/bulk, so the service can fetch many product IDs in one call. That keeps launch pages fast. The PriceUpdated event then goes to the cache invalidation consumer and the audit and history consumer. One updates the cache and the version index. The other writes audit log and compliance records. This work runs in the background, so it does not slow the user response.
5. Explain rollback, consistency, and operations
Rollback is not a delete. It creates a new version that points back to the older price, and then it publishes another PriceUpdated event. That keeps the history clean and makes rollback safe. The read path may still show an older value for a short time, because cache and read replicas can lag behind the main database. The service layer runs as stateless Java 21 or Java 25 replicas. The read service can use virtual threads to handle many blocking reads. The diagram also shows multi-AZ deployment, autoscaling, circuit breakers, retries with backoff, health checks, TLS, secrets management, least privilege access, and input validation. The main trade-off is simple: we accept a little delay in some reads so the common read path stays very fast.
Engineering Considerations / Design Trade-offs
The benefit is that most reads come from cache, so the service feels fast. The downside is that cache and read replicas can be a little behind the main database. That means a new price may not appear everywhere at the exact same moment. The version field helps the app check freshness and support rollback. Bulk reads also help because the service can fetch many prices in one call instead of many small calls. We accept this because the system is read-heavy, so speed matters more than instant updates on every copy.
Why Interviewers Ask This
They want to see if you can separate fast reads from safe writes. They also want to know if you choose the main database as the source of truth, use cache the right way, and keep history for rollback. This question checks judgment, not memorization. A strong answer shows that you can explain trade-offs, handle background work, and keep the system easy to operate.
Interviewer may ask next
What if marketing wants launch-page prices to stay fresh during a big sale?
I would keep the same basic design, but I would make the launch-page cache fresher. The Price Read Service would still check cache first, but I would warm the hottest product IDs more often and use a shorter TTL for the public snapshot. If the page is anonymous, the CDN can still help, but only for data that can be a little old. For logged-in users, I would rely more on the service cache and less on the CDN. I would also watch the event lag closely and alert if invalidation falls behind. The main database would still be the source of truth, so rollback and audit history stay correct. The downside is more cache work and less reuse from older cached data.
What if the same price update request might be sent twice?
I would keep the same write path, but I would add a stronger check for repeated updates. The Price Write Service should treat a second request with the same product and version as the same change, not as a new price. That keeps the price_records history clean and avoids duplicate writes. Rollback still works, because rollback is just another new version that points back to the older price. The cache invalidation event would stay the same, so the read side still stays fresh. I would also keep the audit consumer, because it gives a clear trail of what changed and when. The downside is a little more checking on every write, but that is worth it for safe updates in practice.
16. How would you design a secure API for an enterprise AI copilot product?System DesignHardMicrosoft
i Question Details
Design a secure API for an enterprise AI copilot product. Discuss multi-tenant authentication, authorization across users and tools, token issuance and revocation, replay protection, misuse prevention, and scaling concerns.
Short Interview Answer (30-60 seconds)
At a high level, this API must let employees use an AI copilot without crossing tenant or permission boundaries. The hardest part is trusting every request, token, and tool call while keeping latency reasonable. I would explain three flows: identity and token handling, the protected copilot request path, and the security side paths for revocation, audit, and monitoring. Stateless Java replicas make scaling easier. The trade-off is that stronger checks add latency and shared security state.
Detailed Explanation
The goal is to let employees safely use an enterprise AI copilot. Every request must stay inside the correct tenant. Users and agents must only reach tools and data they are allowed to use. Replayed requests and revoked credentials must be rejected. The system must also limit misuse and record important security activity. The diagram organizes the solution into identity and token handling, a protected Java request pipeline, controlled tool and model access, and separate policy, revocation, audit, and monitoring paths.
Useful Questions to Ask the Interviewer
How quickly must a revoked session, client, or key lose access?
Which enterprise tools can each tenant use?
Which clients must provide DPoP or nonce-based replay protection?
What tenant quotas and safety rules are required?
How much added latency is acceptable for security checks?
How to Explain It in an Interview
1. Start with identity and token handling
I would start by establishing identity before any copilot request runs. The Enterprise IdP handles OIDC or SAML login. It connects to the Token Issuer / Auth Service.
The Token Issuer / Auth Service creates short-lived JWT access tokens plus refresh or service tokens. Tokens carry tenant, subject, scopes, tool grants, jti, and expiration claims. JWKS provides the public keys used to verify signatures.
The Admin Console can also start a session, client, or key revocation. The diagram shows token and revocation updates feeding the security paths used by the copilot service and audit flow.
2. Protect the API entry point
Client traffic enters through the API Gateway / Edge. It applies TLS or mTLS, WAF checks, tenant quotas, and schema or payload validation.
The request then enters the Secure Copilot API Service. It runs as stateless Java 21+/25 JVM replicas. Virtual-thread request handlers support many blocking operations, while bounded backpressure stops the service from accepting unlimited work.
3. Validate each request in a fixed order
The Request Validator checks payload shape and size. Token Validation checks the signature, claims, expiration, clock skew, and cached JWKS data.
The Tenant Resolver establishes the tenant context and keeps tenants isolated. Replay Protection checks jti plus a nonce or DPoP proof, along with a short TTL and clock-skew checks.
Authorization / Policy Decision then evaluates RBAC, ABAC, scopes, tool grants, and resource entitlements. It performs a policy lookup in the Policy Store. Revocation and nonce checks use the Revocation Cache + Nonce Store.
4. Control model and tool access
Misuse Prevention applies quotas, allow-lists, DLP and PII checks, prompt and response safety, and anomaly detection. The Copilot Orchestrator plans the request, builds context, applies guardrails, and shapes the response.
The Tool / Model Adapter sends a delegated scoped token to Approved Enterprise Tools. It sends prompt and context requests to the Model Gateway / LLM Service. Responses return through the adapter and service before the filtered JSON response goes back to the client.
5. Scale and observe the security path
The Audit Log / Event Stream records structured requests, policy decisions, tool calls, errors, and token events. It sends abuse or anomaly signals to Observability & Security Analytics for dashboards, alerts, threat hunting, anomaly detection, and compliance reporting.
The service scales horizontally with stateless replicas. It caches JWKS and policies, partitions shared stores, and keeps audit work asynchronous. The main trade-off is extra latency and operational complexity in exchange for stronger tenant isolation, replay protection, authorization, and auditability.
Engineering Considerations / Design Trade-offs
The benefit is strong separation between tenants, users, and tools. Short-lived tokens reduce the damage from stolen credentials. Replay checks, policy checks, quotas, and safety checks add more protection. Stateless replicas also make horizontal scaling easier. The downside is extra work on every request. Policy, revocation, and safety checks can add latency. Cached JWKS and policies reduce that cost, but the system must keep those caches fresh enough. Shared policy and revocation stores also add operational work. We accept this complexity because enterprise security, controlled tool access, and auditability are more important than having the simplest possible request path.
Why Interviewers Ask This
Interviewers use this question to test security and system-design judgment. They want to see whether you can isolate tenants, combine user and tool permissions, manage tokens safely, stop replay attacks, limit misuse, and still scale the service. They also want clear trade-offs. A strong answer explains where each security check belongs and why it is needed.
Interviewer may ask next
What would you change if token revocation had to take effect almost immediately for every tenant?
I would keep the same architecture, but make the revocation path more important on every protected request. The Token Issuer / Auth Service would still issue short-lived access tokens. It would also send revocation updates into the security path shown in the diagram.
The Secure Copilot API Service would continue checking the Revocation Cache + Nonce Store before sensitive work proceeds. That store keeps revoked JTIs, session status, nonces, and TTL information. The check remains after token validation and before the request reaches protected tool actions.
Short token lifetimes still provide a second safety limit if a revocation update is delayed. The Admin Console remains the place that starts session, client, or key revocation.
The downside is stronger dependence on the revocation store. Slow revocation checks can increase request latency, so the service still needs bounded backpressure instead of accepting unlimited waiting work.
How would this design handle a tenant that allows only a small set of enterprise tools?
I would keep the same design and make that tenant's policy more restrictive. The Tenant Resolver first establishes the tenant context. Authorization / Policy Decision then checks RBAC, ABAC, scopes, tool grants, and resource entitlements from the Policy Store.
Only actions that pass those checks should reach the Copilot Orchestrator. The Tool / Model Adapter then sends a delegated scoped token to Approved Enterprise Tools. That token limits the downstream action to the permissions approved for the user and tenant.
Misuse Prevention still applies quotas, allow-lists, DLP and PII checks, and prompt or response safety. The Audit Log / Event Stream continues recording policy decisions and tool calls so security teams can review what happened.
The downside is more policy management. Different tenants can have different roles and tool grants, so policy data must stay correct and easy to audit.
17. How would you design a message queue with producer-consumer semantics?System DesignMediumMicrosoft
i Question Details
Design a message queue with producer-consumer behavior and cover synchronization, mutexes versus semaphores, deadlock prevention, retries, and throughput trade-offs. Explain how you would keep the design safe and scalable.
Short Interview Answer (30-60 seconds)
At a high level, the goal is to accept messages safely and let consumers process them independently. The main challenge is coordinating many producers and consumers without losing work or letting memory grow without limit. I would explain three flows: producing, consuming, and retrying failed work. Messages are durably appended before the producer gets an ACK. Consumers fetch messages and ACK successful processing. Partitioning increases throughput, but ordering is guaranteed only inside each partition.
Detailed Explanation
The system must let many producers submit messages while many consumers process them safely. The difficult part is coordinating concurrent work without losing messages, filling memory without limit, or getting stuck when a consumer fails. The diagram solves this with a durable partitioned queue, bounded buffers, ACK-based delivery, retries, and clear Java JVM boundaries. I would explain the producer path first, then synchronization, message delivery, failure handling, and scaling.
Useful Questions to Ask the Interviewer
Do we need ordering across the whole queue or only inside each partition?
How many retries should happen before a message moves to the Dead Letter Queue?
How long should the visibility timeout or lease be for a consumer?
How much queue growth should we allow before applying backpressure?
How to Explain It in an Interview
1. Explain the producer path
For the write path, Producer Applications run in separate JVMs. They send requests through the API Gateway / Load Balancer. The request then passes AuthN / AuthZ and Validation & Rate Limits before entering the Message Queue Platform.
The Enqueue API accepts the message, and the Partition Router selects a partition. The platform appends the message to the Durable Partitioned Queue Store. The store uses partitioned append-only logs and keeps data durable. Only after that durable append succeeds does the producer receive an ACK and Message ID through the ingress path. This order avoids confirming work that was not safely stored.
2. Explain synchronization and backpressure
Inside each partition, the platform uses a bounded buffer. A ReentrantLock works as a mutex, so only one thread changes critical buffer metadata at a time. This protects the read position, write position, and related shared state.
Counting semaphores coordinate availability. availableSlots tracks free capacity. availableMessages tracks messages ready to consume. A producer must wait when no slot is free, which creates backpressure. A consumer waits when no message is ready.
Deadlocks are reduced with fixed lock ordering and small critical sections. Locks are not held during I/O. Timed tryLock calls also prevent indefinite waiting.
3. Explain message delivery and ACKs
Consumer Groups run in separate JVMs. Worker Threads, including platform or virtual threads, run inside those JVMs. Consumers fetch messages using the Dequeue API / Long Poll path.
The Queue Service can read messages from the Durable Partitioned Queue Store. The Partition Router and Scheduler / Dispatcher help route available work. After processing succeeds, the consumer sends an ACK to the Ack & Retry Manager.
The platform also reads and writes the Metadata / Coordination Store. It keeps queue and partition definitions, offsets, leases or visibility timeouts, retry counts, ownership, consumer-group state, and membership information.
4. Explain retries and failed messages
If a consumer fails or its ACK times out, the Ack & Retry Manager does not mark the message complete. It requeues the message with exponential backoff, which means waiting longer between repeated attempts.
This design provides at-least-once delivery. A message can therefore be processed more than once. Consumers should be idempotent, meaning repeated processing should not create an incorrect duplicate result. After the retry limit is reached, the message moves to the Dead Letter Queue for investigation and later replay.
5. Explain scaling and trade-offs
The system scales by adding Queue Service replicas, partitions, and consumer instances. More partitions allow more work to happen in parallel. The Metadata / Coordination Store coordinates partition ownership and consumer-group state.
Batching and partitioning increase throughput, meaning more messages can be handled per second. The trade-off is that ordering is only guaranteed inside one partition. Stronger delivery guarantees can also reduce throughput. The Durable Partitioned Queue Store can retain messages by time or size. Observability collects metrics, logs, traces, and alerts for latency, queue depth, lag, retries, DLQ growth, and consumer health.
Engineering Considerations / Design Trade-offs
The benefit is that durable writes and ACKs make message handling safer. The bounded buffer also protects the service when producers send work faster than consumers can finish it. More partitions, Queue Service replicas, and consumers increase throughput because more work can happen in parallel. The downside is more coordination and more state to manage. Ordering is only guaranteed inside one partition. At-least-once delivery may send the same message again after a failure, so consumers must handle duplicates safely. Batching improves speed, while stronger delivery guarantees usually reduce throughput.
Why Interviewers Ask This
Interviewers use this question to see whether you can connect Java concurrency with system design. They want to know if you understand mutexes, semaphores, backpressure, durable storage, ACKs, retries, and consumer failures. They also check whether you can avoid deadlocks, scale with partitions and replicas, and explain the safety-versus-throughput trade-off clearly.
Interviewer may ask next
What would you change if consumers sometimes take a long time to process a message?
I would keep the same design, but I would tune the lease or visibility timeout stored in the Metadata / Coordination Store. The timeout must be long enough for normal processing. Otherwise, the Ack & Retry Manager may assume the consumer failed and requeue a message that is still running.
If processing really exceeds the timeout, the message can be delivered again. That is still compatible with the diagram's at-least-once model because consumers should be idempotent. In simple terms, processing the same message twice should not create two incorrect results.
I would also watch end-to-end latency, consumer health, queue depth, lag, and retry rates in Observability. These signals help show whether consumers are simply slow or actually failing.
The downside is that a longer timeout delays recovery when a consumer truly dies. A shorter timeout detects failures sooner, but it creates more unnecessary duplicate processing.
How would you increase throughput if the queue becomes much busier?
I would scale the parts already shown in the diagram. First, I would add more partitions to the Durable Partitioned Queue Store. More partitions let the Queue Service handle independent message streams in parallel. I would also add more Queue Service replicas and more Consumer Group worker instances.
The Metadata / Coordination Store would continue tracking partition ownership, consumer-group state, offsets, leases, and membership. This keeps ownership coordinated while the system grows. Batching can also reduce the cost of handling each individual message.
The bounded buffer and semaphores still provide backpressure when producers are faster than consumers. That prevents unlimited in-memory growth.
The main downside is that more partitions add coordination work. They also limit ordering. Messages remain ordered only inside one partition, so increasing parallelism makes global ordering harder without giving up much of the throughput gain.
18. How would you design a multi-level parking lot?System DesignMediumMicrosoft
i Question Details
Design a multi-level parking lot and cover vehicles, tickets, floors, slots, and the basic request flow. Include class structure, handling multiple entry/exit points, closest spot selection, payments, concurrency for simultaneous arrivals, and any tradeoffs you would discuss in an interview.
Short Interview Answer (30-60 seconds)
At a high level, this is a system for guiding cars through a parking lot with many floors and many gates. The main challenge is keeping spot, ticket, and payment data correct when many cars arrive at once. I would explain it in three parts: how a car enters and gets a ticket, how the system finds and reserves a spot, and how payment and exit release the spot again. The main trade-off is that faster cache reads help performance, but the main database still has to stay correct.
Detailed Explanation
The goal is to help drivers park, pay, and leave without confusion. The hard part is that many cars can arrive at the same time, and the system must still know which spot is free, which ticket belongs to which car, and whether payment is done. The diagram keeps the fast entry and exit paths separate from the storage and background work. I would explain it from the gate, to the parking logic, to payment, and then to the background events.
Useful Questions to Ask the Interviewer
Should the system reserve a spot only when the car arrives, or also allow advance booking?
Is pricing fixed, or can it change by floor, time, or vehicle type?
Should drivers pay only at exit, or can they pay by app or kiosk earlier?
How to Explain It in an Interview
1. Explain the goal and the main idea
At a high level, the goal is simple: guide cars into the right spot and let them leave cleanly. The main challenge is keeping the spot map correct when many drivers arrive together. The diagram solves this by splitting the flow into entry, spot selection, payment, exit, storage, and background work.
2. Explain the entry and ticket path
For the create path, a driver enters through one of the multiple entry points. The request first goes through the API Gateway for auth, validation, rate limiting, and request routing. Then Ticket Service creates the ticket and connects it to the vehicle and entry point. Spot Allocation Service finds the closest spot, reserves it, and assigns a floor and slot. Parking Space Service and Level Service keep the floor and slot state up to date.
3. Explain how the parking state stays correct
The simplified class box makes the domain easy to see. ParkingLot owns many Level objects. Each Level owns many ParkingSpot objects. EntryGate, Vehicle, Ticket, and Payment are the other main objects. The main database is the source of truth for tickets, vehicles, floors, slots, and payments. The diagram also shows a PostgreSQL primary database and a read replica for read scaling. Redis is only a fast helper for slot availability and floor summary. If the cache misses, the service checks the main database and then refreshes the cache. Object storage keeps receipts, invoices, and audit files.
4. Explain the exit, fee, and payment path
For the exit path, the driver reaches any exit gate with the ticket or QR code. Exit Service validates the ticket, asks Payment Service to calculate the fee, and then processes payment through the external Payment Gateway. After payment succeeds, the system releases the spot and closes the ticket. The receipt is saved, and the driver gets the result. Notification Service can also send SMS or email messages when needed. The Admin Service manages rates, blocked slots, and reports.
5. Explain background work, scale, and trade-offs
The diagram also shows background work. Event Publisher sends events like SlotReserved, PaymentCompleted, SlotReleased, AuditEvent, and LowInventory. Background workers then generate reports, clean expired reservations, send notifications, and handle payment checks in the background. This work does not block the main user flow. For scale, the services are stateless Spring Boot apps on Java 21/25 behind a load balancer. Virtual threads help with blocking database and payment calls. The tech stack also includes PostgreSQL, Redis, Kafka or RabbitMQ for events, REST APIs over HTTPS, and Micrometer with Prometheus and Grafana for monitoring. Row locks or optimistic locking protect slot reservations. The trade-off is simple: cache and background work make the system faster, but the main database still has to stay correct.
Engineering Considerations / Design Trade-offs
The benefit is that the system stays fast for drivers and still keeps the main data correct. Redis helps with quick spot checks, but it is not the source of truth. The downside is that we now have more moving parts, so the design is harder to build and test. Another trade-off is between parking speed and spot accuracy. Stronger locking keeps the data safe, but it can slow peak traffic a little. Background workers also help, but they make some reports and alerts arrive later.
Why Interviewers Ask This
The interviewer wants to see if you can turn a real problem into simple flows. They want to know if you can keep the ticket, spot, and payment data correct. They also want to see that you understand cache use, concurrency, and background work. Most of all, they want clear thinking and clear trade-offs, not memorized words.
Interviewer may ask next
What would you change if many cars arrive at the same time and the lot is almost full?
I would keep the same basic design, but I would make the spot reservation step stricter. The Spot Allocation Service and Parking Space Service would need strong locking around the chosen spot so two cars do not get the same slot. Redis would still help with quick availability checks, but the main database would decide the final state. That keeps the parking map correct even under heavy load. If the lot is almost full, the LowInventory event can also trigger alerts or reports in the background. The downside is that stronger locking can slow down the busiest moments a little.
What if the payment gateway is slow or fails during exit?
I would keep Payment Service as the owner of that part of the flow, but I would add a clear timeout and retry path. If the gateway is slow, Exit Service can keep the ticket open and show the driver a pending state. The payment attempt can also be recorded as an event so background workers can check it later. Once payment succeeds, the system closes the ticket and releases the spot. Receipts can still be saved in object storage. The downside is that the exit may take longer when the external payment service is unhealthy.
19. How would you design a ticket booking system?System DesignMediumMicrosoft
i Question Details
Design a ticket booking system that handles failures, dropped requests, race conditions, and safe booking under contention. Explain the main components, reservation flow, concurrency controls, retries, and how you would prevent double booking.
Short Interview Answer (30-60 seconds)
At a high level, this system must let users reserve seats without selling the same seat twice. The hardest part is keeping booking writes correct when many users compete for one seat or retry after a lost response. I would explain it through the seat-read path, the reservation and payment path, and the failure and background paths. The database is the source of truth, while the cache speeds up seat-map reads. The trade-off is stronger booking correctness with more database coordination.
Detailed Explanation
The goal is to let a customer choose a seat, reserve it for a short time, pay, and receive a confirmed booking. The difficult part is that several customers may try to reserve the same seat together. Requests can also time out, responses can be lost, and payments can fail. The design handles this by keeping seat writes strongly controlled in the Relational Booking Database. It uses a separate Seat Availability Cache for fast display reads. Ticket creation, notifications, and expired-hold cleanup happen after the main booking work.
Useful Questions to Ask the Interviewer
How long should a temporary seat HOLD remain valid?
Should a payment timeout return a PENDING result or an immediate failure?
How quickly should the displayed seat map reflect a new HOLD or BOOKED seat?
How to Explain It in an Interview
1. Explain how requests enter the system
I would start by saying that the Client sends a booking request with the seat choice and an idempotency key. The Entry Layer, which contains the Load Balancer or API Gateway, forwards the request to the Java Booking Service.
Before booking work starts, Security and Request Protection checks authentication, request validation, rate limits, and the idempotency key. The idempotency key lets the system recognize the same request when a client retries after a dropped response.
2. Separate seat-map reads from booking writes
For displaying seat availability, the Java Booking Service can read from the Seat Availability Cache. This keeps seat-map reads fast.
The cache is only the read path. The Relational Booking Database remains the source of truth for seat state. Booking decisions therefore use the database instead of trusting possibly older cached data.
3. Create the HOLD safely
For a booking, Reservation and Inventory Logic starts a database transaction and creates a short HOLD only when the seat is still available.
The database uses a transaction with a row lock, or a conditional update with a version check. It also keeps a unique active-seat constraint so only one active HOLD or BOOKING can exist for a seat. Under contention, one request wins and the others return seat unavailable. This is the main protection against double booking.
The Java Booking Service has stateless replicas running in separate JVM processes. The replicas do not share heap state. Java 21 or Java 25 virtual threads can handle many concurrent blocking database and network calls, but booking correctness still comes from the database.
4. Handle payment and confirmation
After the HOLD succeeds, the service calls the external Payment Service. If payment succeeds, the service confirms the booking by changing HOLD to BOOKED.
It also stores the idempotency result and writes an Outbox Event. The confirmation is then returned to the Client. If the original response was dropped, the Client retries with the same idempotency key. The service returns the previous result or the current in-progress status instead of creating another booking.
5. Handle failures and background work
If payment fails or times out, the HOLD can be marked FAILED or PENDING. The system returns a failure or pending result. The seat is either released or allowed to expire.
Outbox Events flow through the Message Queue or Outbox Flow. A Background Worker in a separate JVM process consumes them. It can issue the ticket, send a notification, update the search index, and perform other side effects.
The Hold Expiry or Cleanup Worker finds expired HOLDS and returns those seats to AVAILABLE. A short hold time improves recovery from abandoned reservations. Observability provides metrics, logs, traces, alerts, and dashboards across requests, database work, payments, and background processing.
Engineering Considerations / Design Trade-offs
The benefit is strong protection against double booking. The Relational Booking Database controls seat writes, so only one request can win for a seat. The Seat Availability Cache makes display reads faster, but booking correctness does not depend on it. Idempotency makes retries safe after a dropped response. The downside is extra database coordination because booking requests need transactions and contention checks. Short HOLDS improve recovery, but they temporarily block a seat. Background workers keep ticketing and cleanup away from the main request, but they add more processes that must be monitored.
Why Interviewers Ask This
Interviewers ask this question to see whether you can protect shared data when many users act at the same time. They want to know whether you can prevent double booking, separate fast reads from correct writes, handle retries after lost responses, and deal with payment failures. They also test whether you understand database concurrency, background work, Java process boundaries, and the trade-offs between speed, correctness, and operational complexity.
Interviewer may ask next
What would you change if thousands of users try to reserve the same popular seat at the same moment?
I would keep the same basic design because the Relational Booking Database already owns the final booking decision. Every attempt for that seat would still go through the database transaction instead of trusting the Seat Availability Cache.
The transaction would use the same row lock or conditional update with a version check. The unique active-seat constraint would remain the final safety check. Only one request can create the valid HOLD. Competing requests would fail and return seat unavailable.
The stateless Java Booking Service replicas can scale horizontally to receive more requests. Virtual threads can help each JVM handle many blocking calls. Rate limiting can also reduce abusive or repeated traffic before it reaches the booking logic.
The main downside is contention on that one seat. More application replicas do not remove the fact that every request for the same seat must compete for the same protected database state.
What happens if payment succeeds but the client never receives the booking confirmation?
I would use the existing idempotency flow. After payment succeeds, the service changes the HOLD to BOOKED, stores the idempotency result, and writes the Outbox Event as part of the confirmation work.
If the response is dropped, the Client sends the request again with the same idempotency key. Security and Request Protection checks that key. The service then returns the previous result or the current in-progress status instead of creating another HOLD or booking the seat again.
The Background Worker can still process the Outbox Event and issue the ticket or send the notification. That work does not depend on the Client receiving the first response.
The main downside is temporary uncertainty for the Client. The retry may be needed to learn the final status, so the idempotency record must remain available long enough for that retry.
20. How would you design an undo/redo mechanism for a text editor with configurable limits?System DesignMediumMicrosoft
i Question Details
Design an undo/redo mechanism for a text editor with configurable history limits. Explain the data structures for history tracking, how you would handle redo invalidation, memory limits, and the operations exposed by the API.
Short Interview Answer (30-60 seconds)
At a high level, the goal is to let users move backward and forward through text edits safely. The main challenge is keeping history correct without using unlimited memory. I would explain three flows: a new edit, undo, and redo. Each edit becomes a Command. Commands move between undo and redo stacks as the user navigates history. A new edit clears the redo stack. Configurable command and byte limits remove old history, while delta-based commands avoid full document copies.
Detailed Explanation
The goal is to let a user edit text, undo earlier changes, and redo them when needed. The difficult part is keeping the document and its history in exactly the same order while preventing history from using unlimited memory. The diagram solves this with Command objects, two history stacks, a memory-aware limit policy, and an efficient DocumentBuffer. A new edit follows one path, while undo and redo move existing commands between the two stacks. The design also groups nearby typing when useful and keeps all changes for one document serialized.
Useful Questions to Ask the Interviewer
Should history be limited by command count, memory use, or both?
Should nearby typing actions be combined into one undo step?
Can several threads edit the same document, or is editing serialized?
How to Explain It in an Interview
1. Start with the command model
I would represent each user change as a Command. The Command abstraction supports execute(doc), undo(doc), redo(doc), and memoryCost(). Concrete examples are InsertCommand, DeleteCommand, ReplaceCommand, and CompoundCommand.
The EditorController / TextEditor API exposes applyEdit(EditOp), undo(), redo(), canUndo(), canRedo(), beginCompoundEdit(), endCompoundEdit(), setHistoryLimits(maxCommands, maxBytes), and clearHistory(). These components run inside the Text Editor JVM shown in the diagram.
2. Explain the new edit flow
For a normal change, the User / Editor UI calls applyEdit(EditOp). The EditorController sends the edit to CommandFactory, which builds a concrete Command with the forward change and the inverse information needed for undo.
The Command calls execute(doc) on the DocumentBuffer. The diagram uses a Piece Table, which supports insert, delete, and replace operations without making a full document copy for each change. Only after the document change succeeds does the UndoRedoManager commit that Command to history.
The manager pushes the Command onto undoStack, which is an ArrayDeque<Command>. It then clears redoStack. This is redo invalidation. After an undo, a new committed edit creates a different future, so the old redo branch is no longer valid.
3. Explain undo and redo
For undo, the User / Editor UI calls undo(). The UndoRedoManager takes the latest Command from undoStack and calls undo(doc). That Command reverses its change in the DocumentBuffer.
The same Command then moves to redoStack. For redo, the manager takes the latest Command from redoStack, calls redo(doc), and moves that same Command back to undoStack. The Command is moved, not duplicated, so the two stacks do not create a second copy of its history data.
4. Explain configurable limits and memory
HistoryConfig & LimitPolicy tracks maxCommands, maxHistoryBytes, commandCount, and historyBytes. The UndoRedoManager updates these values when history changes.
If history exceeds a configured limit, the policy evicts the oldest undo entries until history fits both limits. Commands store deltas instead of full document copies. For DeleteCommand or ReplaceCommand, this includes the removed text and its position so undo can restore it correctly.
Adjacent typing can be combined into a CompoundCommand. This avoids creating one history entry for every keystroke and gives the user more natural undo steps.
5. Keep document state and the UI consistent
The Event / UI Notifier publishes document changes and canUndo or canRedo state to the UI. This keeps the displayed document and toolbar state consistent with the history stacks.
Edits for one document must be serialized. A single UI thread or a lock can provide that ordering. This prevents an edit, undo, and redo from changing the DocumentBuffer and history stacks at conflicting times.
Engineering Considerations / Design Trade-offs
The benefit is that two stacks make undo and redo easy to reason about. Moving the same Command between them also avoids duplicating history objects. Storing deltas uses much less memory than copying the whole document after every edit. The downside is that some commands still need to keep removed text so they can restore it later. maxCommands and maxHistoryBytes keep memory bounded, but old undo entries may be lost. Grouping typing into CompoundCommand entries improves usability, but the editor must decide where one typing group ends. Serializing edits keeps history correct, but one document cannot process conflicting edits in parallel.
Why Interviewers Ask This
Interviewers ask this to see whether you can turn user actions into clear state changes. They want to know if you understand undo and redo stacks, redo invalidation, memory limits, and clean API design. They also look for practical judgment. Good answers explain why commands store inverse data, why old history may be removed, and how document state stays consistent when several operations could happen close together.
Interviewer may ask next
How would you change the design if several threads could edit the same document?
I would keep the same EditorController, UndoRedoManager, Command objects, DocumentBuffer, undoStack, and redoStack. The important change is that all operations for one document must run in one clear order.
A simple option is to keep edits on one UI thread. If several threads can call the API, I would use a lock owned by that document. The protected section must include the DocumentBuffer change and the matching history update.
For example, an undo must not pop a Command while another thread is halfway through committing a new edit. One operation must finish before the other starts. This keeps the document, undoStack, redoStack, command count, and history byte count consistent.
The main downside is reduced parallel work for one document. That is usually acceptable because undo and redo already require a single ordered history.
What happens if one large delete command almost reaches the configured memory limit by itself?
I would keep the same history design and use the Command's memoryCost() when enforcing maxHistoryBytes. A DeleteCommand must keep the removed text and its position because undo needs that information to restore the document correctly.
After the command is successfully committed, the UndoRedoManager updates historyBytes. HistoryConfig & LimitPolicy then removes the oldest undo entries until the history fits maxHistoryBytes and maxCommands. One large delete may therefore cause several older commands to be evicted.
I would not replace the command with a full document snapshot because that would normally use even more memory. The current delta-based approach remains consistent with the diagram.
The downside is a shorter undo history when edits contain large amounts of removed text. The benefit is predictable memory use while the most recent retained commands still undo correctly.
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.