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.
1. How would you determine dictionary-based string segmentation?CodingMediumMicrosoft
i Question Details
Given a string and a dictionary of tokens, determine whether the string can be segmented and return one valid segmentation if possible.
Short Interview Answer (30-60 seconds)
I would use dynamic programming with a predecessor array. I let dp[i] mean that the prefix s[0..i) can be split into dictionary words. For each end position, I try earlier start positions whose prefixes are already reachable and check the substring in a HashSet. When I find a valid word, I record the cut in prev. Then I follow prev backward to rebuild one segmentation. The time is O(n^2 · L), and auxiliary space is O(n + |dict|).
The input is a string and a dictionary of allowed words. We need to answer two things. First, can the complete string be formed by joining dictionary words? Second, if it can, return one valid list of those words. The diagram uses dynamic programming because the answer for a longer prefix depends on smaller prefixes that were already proved valid. A HashSet makes dictionary membership checks fast on average. A prev array remembers the cuts so we can rebuild the words afterward.
Useful Questions to Ask the Interviewer
Should matching be case-sensitive? The shown Java solution uses exact String matching, so it is case-sensitive.
If more than one segmentation exists, is returning any one valid segmentation enough? The shown solution returns one valid segmentation.
How to Explain It in an Interview
1. Define what the DP state means
Let dp[i] mean that the prefix s[0..i) can be formed completely from dictionary words. Index i is a boundary between characters, not a character index. The base case is dp[0] = true because the empty prefix is already valid.
We also keep prev[i]. If dp[i] becomes true, prev[i] stores the boundary where the final chosen word starts. This lets us reconstruct one valid answer later.
2. Initialize the data structures
Put every dictionary word into a HashSet<String> called dict. This gives average O(1) membership checks. Create boolean[] dp with n + 1 entries and int[] prev with n + 1 entries. Fill prev with -1. Set only dp[0] to true initially.
The main invariant is: whenever dp[i] is true, s[0..i) has a valid segmentation.
3. Process each possible ending boundary
For end from 1 through n, try every start from 0 through end - 1. A candidate word is s.substring(start, end).
We only need to test that word when dp[start] is true. That means everything before start already has a valid segmentation. If the candidate substring is also in the dictionary, then the prefix through end is valid. We set dp[end] = true and prev[end] = start. Because we only need one segmentation, we stop checking other start values for that end.
4. Walk through the exact example
The example is s = "applepenapple" and wordDict = ["apple", "pen"]. The string length is 13.
At end = 5, start = 0 is reachable because dp[0] is true. s.substring(0, 5) is "apple", which is in the dictionary. We set dp[5] = true and prev[5] = 0.
At end = 8, start = 5 is reachable because dp[5] is true. s.substring(5, 8) is "pen". It is in the dictionary, so we set dp[8] = true and prev[8] = 5.
At end = 13, start = 8 is reachable because dp[8] is true. s.substring(8, 13) is "apple". We set dp[13] = true and prev[13] = 8.
Other end positions do not find a dictionary token from a reachable start, so their dp entries remain false. The reachable cut boundaries used by the final solution are 0, 5, 8, and 13.
5. Reconstruct one valid segmentation
Because dp[13] is true, the full string can be segmented. Follow prev backward from 13: 13 -> 8 -> 5 -> 0.
These cuts give the words "apple", "pen", and "apple". The returned result is canSegment = true with words = ["apple", "pen", "apple"]. Joining them gives "applepenapple", so they cover the whole input exactly. This is one valid segmentation.
6. Explain why it is correct
The invariant says dp[i] is true exactly when s[0..i) is segmentable. If dp[start] is true and s[start..end) is a dictionary word, then adding that word to the valid prefix gives a valid segmentation ending at end. Therefore setting dp[end] to true is correct. prev[end] records a valid final cut. Following those predecessor links backward reconstructs one valid segmentation.
7. Explain complexity and edge cases
The nested loops consider O(n^2) start and end pairs. HashSet membership is O(1) on average. In modern Java, substring(start, end) creates a new String and copies characters, so the diagram expresses the time as O(n^2 · L), where L is the checked substring length. Auxiliary space is O(n + |dict|), plus the returned words. An empty string returns a valid empty segmentation. If no complete split exists, dp[n] stays false. Dictionary words may be reused, and matching is case-sensitive.
Key Insight / Why This Solution Works
The key idea is to solve the problem by valid prefix boundaries. dp[i] records whether s[0..i) can be formed from dictionary words. The central invariant is that every true dp position represents a prefix with a valid segmentation. For each end boundary, we look for a reachable start boundary where s[start..end) is in the HashSet. When one is found, prev[end] stores that start. The predecessor array is important because a boolean DP alone tells us whether segmentation exists, but it cannot reconstruct the actual words. Since only one valid segmentation is required, the first valid predecessor for an end boundary is enough.
Code
import java.util.*;
publicclassMain {
staticclassSegmentationResult {
finalboolean canSegment;
final List<String> words;
SegmentationResult(boolean canSegment, List<String> words) {
this.canSegment = canSegment;
this.words = words;
}
}
static SegmentationResult segmentString(String s, List<String> wordDict) {
// Put dictionary tokens in a HashSet for average O(1) membership checks.
Set<String> dict = newHashSet<>(wordDict);
intn= s.length();
// dp[i] is true exactly when prefix s[0..i) can be segmented.boolean[] dp = newboolean[n + 1];
// prev[i] stores the previous cut boundary used to reach boundary i.// A value of -1 means no predecessor has been recorded yet.int[] prev = newint[n + 1];
Arrays.fill(prev, -1);
// The empty prefix is the base case and is already segmentable.
dp[0] = true;
// Process every possible ending boundary from left to right.for (intend=1; end <= n; end++) {
// Try earlier boundaries as the start of the last dictionary token.for (intstart=0; start < end; start++) {
// We can extend only a prefix that is already known to be valid.if (!dp[start]) {
continue;
}
// If s[start..end) is a dictionary word, this end becomes reachable.if (dict.contains(s.substring(start, end))) {
dp[end] = true;
prev[end] = start;
// One predecessor is enough because we need only one segmentation.break;
}
}
}
// If boundary n is unreachable, no complete segmentation exists.if (!dp[n]) {
returnnewSegmentationResult(false, Collections.emptyList());
}
// Follow predecessor links backward and rebuild the chosen words.
LinkedList<String> words = newLinkedList<>();
for (intidx= n; idx > 0; idx = prev[idx]) {
// addFirst restores forward order while we walk from right to left.
words.addFirst(s.substring(prev[idx], idx));
}
// The list now contains one valid segmentation of the whole string.returnnewSegmentationResult(true, words);
}
publicstaticvoidmain(String[] args) {
// Run the exact example from the approved diagram.Strings="applepenapple";
List<String> wordDict = List.of("apple", "pen");
SegmentationResultresult= segmentString(s, wordDict);
// Expected output: true with [apple, pen, apple].
System.out.println("canSegment = " + result.canSegment);
System.out.println("words = " + result.words);
}
}
Time & Space Complexity
Let n be the string length. The dynamic programming loops can examine O(n^2) start and end pairs. Looking up a word in the HashSet is O(1) on average. However, Java substring(start, end) creates a new String and copies its characters. If L is the length of a checked substring, the diagram describes the running time as O(n^2 · L). When L can be O(n), the character-copy work can reach O(n^3) in the worst case. The main auxiliary memory is O(n + |dict|) for dp, prev, and the dictionary set, plus the returned words.
Where it is used
This pattern is useful when text must be split using a known vocabulary. Examples include tokenization, command parsing, dictionary-based text processing, and checking whether an input can be composed from allowed terms. The predecessor-array pattern is also useful in dynamic programming problems where we need not only to know that a solution exists, but also to reconstruct one actual solution.
Why Interviewers Ask This
This problem tests whether you can recognize a prefix dynamic programming pattern and define its state precisely. It also checks whether you can connect an existence test with reconstruction by storing predecessor information. The interviewer can evaluate your understanding of substring boundaries, dependency order, HashSet lookup behavior, and invariants. It also tests practical Java knowledge because substring creation affects the real complexity and because the implementation must handle no valid split, an empty string, repeated dictionary words, and exact case-sensitive matching.
Common interview mistakes
A common mistake is defining dp[i] without making clear that i is a boundary and dp[i] represents s[0..i). Another mistake is treating a substring like a subsequence. The algorithm requires contiguous characters from start to end. Candidates may also forget the prev array, which means they can detect that a segmentation exists but cannot return the actual words. Another mistake is continuing to search after finding a valid predecessor even though only one segmentation is required. Finally, claiming simple O(n^2) time ignores the character-copy cost of Java substring creation in this implementation.
Interview tip
State the invariant before coding: dp[i] means s[0..i) is segmentable, and prev[i] remembers the last cut. Then walk through the boundaries 0 -> 5 -> 8 -> 13 from the example. This makes both the DP update and the reconstruction easy for the interviewer to follow.
Interviewer may ask next
How would you change the solution if you had to return all valid segmentations instead of only one?
A single prev[i] would no longer be enough. For each reachable end boundary, I would store every valid predecessor start instead of stopping after the first one. After building those predecessor lists, I would traverse them from n back to 0 to generate every valid word sequence. The same reachable-prefix rule preserves correctness. Building the relationships still considers O(n^2) start/end pairs plus substring work, but generating all answers can take exponential time and space because the number of valid segmentations itself can be exponential.
Can the Java implementation reduce the cost of creating many substring objects?
Yes. The current implementation creates a String for each candidate substring that it checks. One related optimization is to organize dictionary words by length so we test fewer ranges, or to use a trie and compare characters directly from each reachable boundary. A boundary is still marked reachable only when a valid dictionary token extends another reachable boundary, so the correctness idea stays the same. The tradeoff is extra implementation and data-structure complexity in exchange for less substring allocation.
2. How would you rotate an array by k elements?CodingEasyMicrosoft
i Question Details
Rotate an array by one step and then by k steps. Explain the in-place technique, edge cases, and complexity.
Short Interview Answer (30-60 seconds)
I would rotate the array in place using three reversals. First, I normalize k with k % n. Then I reverse the whole array, reverse the first k elements, and reverse the remaining n - k elements. This moves the last k values to the front while restoring the correct order inside both parts. A one-step rotation uses the same method with k = 1. The time complexity is O(n), and the auxiliary space is O(1).
The goal is to move the last k values of the array to the front without creating another array. The values that were at the front move to the right, and their order must stay correct. For example, rotating [1, 2, 3, 4, 5, 6, 7] to the right by 3 gives [5, 6, 7, 1, 2, 3, 4]. The in-place reversal method fits because it changes the same array while using only a few temporary variables. A one-step rotation uses the same process with k = 1.
Useful Questions to Ask the Interviewer
Should the rotation be to the right, as shown in the problem?
Should I modify the original array instead of returning a new array?
Can k be larger than the array length?
How to Explain It in an Interview
1. Understand the input and required output
The input is an integer array nums and an integer k. We rotate nums to the right by k positions. The array itself is changed in place. For the example nums = [1, 2, 3, 4, 5, 6, 7] and k = 3, the final array is [5, 6, 7, 1, 2, 3, 4]. A one-step rotation is the same operation with k = 1.
2. Choose the in-place reversal method
Let n be the array length. First reduce k with k = k % n. This handles values of k that are larger than the array. Then perform three reversals: reverse [0..n-1], reverse [0..k-1], and reverse [k..n-1]. The reverse helper uses two positions, left and right. It swaps the values at those positions and moves both positions toward the center.
3. Initialize the state
For the example, nums starts as [1, 2, 3, 4, 5, 6, 7]. The indices are [0, 1, 2, 3, 4, 5, 6]. We have n = 7 and k = 3, so k % n is still 3. The three reverse ranges are [0..6], [0..2], and [3..6]. The central invariant is that each reverse operation swaps symmetric endpoints inside its selected range until left is no longer less than right.
4. Walk through the example
Step 1 starts with [1, 2, 3, 4, 5, 6, 7]. We reverse the full range [0..6]. The array becomes [7, 6, 5, 4, 3, 2, 1].
Step 2 starts with [7, 6, 5, 4, 3, 2, 1]. We reverse the first k = 3 positions, range [0..2]. The values [7, 6, 5] become [5, 6, 7]. The array is now [5, 6, 7, 4, 3, 2, 1].
Step 3 starts with [5, 6, 7, 4, 3, 2, 1]. We reverse the remaining range [3..6]. The values [4, 3, 2, 1] become [1, 2, 3, 4]. The final array is [5, 6, 7, 1, 2, 3, 4]. Processing stops after these three reversals.
5. Explain why the result is correct
Reversing the whole array moves the original suffix of length k to the front, but that suffix is backwards. Reversing the first k positions restores that suffix to its original order. The original prefix is also backwards after the full reversal, so reversing the remaining n - k positions restores that part too. Both blocks are now in the correct order and in their rotated positions.
6. Explain the Java implementation
The rotate method first reads nums.length. If the array is empty, it returns immediately. It then normalizes k with k % n. If k becomes 0, the array already has the required order, so it returns. Otherwise, it calls reverse three times using exactly the ranges from the walkthrough. The reverse method swaps nums[left] and nums[right], increments left, and decrements right while left < right.
7. Explain complexity and edge cases
Each reversal touches only part of the array, and the three reversals together do O(n) total work. The algorithm uses O(1) auxiliary space because it swaps values inside the original array using only a few variables. An empty array returns immediately. A one-element array stays unchanged. If k is 0, or k % n is 0, no rotation is needed. If k is larger than n, k % n reduces it to the equivalent rotation. For k = 1, the same method performs a one-step rotation.
Key Insight / Why This Solution Works
The key idea is to turn the right rotation into three in-place reversals. After reversing the whole array, the last k values from the original array are at the front, but their order is reversed. Reversing the first k positions restores that block. Reversing the remaining n - k positions restores the other block. The invariant inside the reverse helper is that each swap places the two current symmetric endpoint values into their correct positions for the reversed range. This method fits the problem because it changes the original array and needs only constant extra memory.
Code
publicclassMain {
publicstaticvoidmain(String[] args) {
int[] nums = { 1, 2, 3, 4, 5, 6, 7 };
intk=3;
// Rotate the same array in place using the three-reversal method.
rotate(nums, k);
// Print the exact final result from the diagram.
System.out.print("[");
for (inti=0; i < nums.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(nums[i]);
}
System.out.println("]");
}
publicstaticvoidrotate(int[] nums, int k) {
intn= nums.length;
// An empty array has nothing to rotate. Return before k % n.if (n == 0) {
return;
}
// Reduce large k values to the equivalent rotation within the array length.
k = k % n;
// If k becomes 0, the array is already in the required position.if (k == 0) {
return;
}
// Move the original suffix of length k to the front, but in reversed order.
reverse(nums, 0, n - 1);
// Restore the moved suffix to its original internal order.
reverse(nums, 0, k - 1);
// Restore the shifted prefix to its original internal order.
reverse(nums, k, n - 1);
}
privatestaticvoidreverse(int[] nums, int left, int right) {
// Swap symmetric endpoints until the selected range is fully reversed.while (left < right) {
inttemp= nums[left];
nums[left] = nums[right];
nums[right] = temp;
// Move both positions toward the center for the next swap.
left++;
right--;
}
}
}
Time & Space Complexity
The time complexity is O(n). Reversing the whole array takes O(n) work. The next two reversals operate on two separate parts whose total length is n. Therefore, all three reversals together still take linear time. The auxiliary space is O(1). Auxiliary space means extra memory used by the algorithm. The code only keeps variables such as n, k, left, right, and temp. It does not create another array whose size grows with the input.
Where it is used
The reversal pattern is useful when software needs to rotate or rearrange a fixed-size array without allocating another array. It is a good fit for memory-sensitive code where changing the original array is allowed and constant extra space is important.
Why Interviewers Ask This
This problem checks whether you can recognize an in-place array transformation instead of immediately using extra memory. It also tests careful index handling, especially the boundaries of the three reverse ranges. The interviewer can see whether you handle k larger than the array length, avoid division by zero for an empty array, reason about why the reversals preserve the required order, write correct Java, and explain O(n) time with O(1) auxiliary space.
Common interview mistakes
A common mistake is forgetting to reduce k with k % n, which causes incorrect ranges when k is larger than the array length. Another mistake is taking k % n before checking whether the array is empty, which would divide by zero when n is 0. Candidates also sometimes reverse the ranges in the wrong order or use the wrong boundary, such as starting the last reversal at k - 1 instead of k. Another mistake is creating a second array and then claiming O(1) auxiliary space. Finally, some candidates rotate left even though the required operation is a right rotation.
Interview tip
Explain the three array states while you code: reverse all, reverse the first k values, then reverse the remaining n - k values. With the diagram's example, the ranges [0..6], [0..2], and [3..6] make every step easy to verify.
Interviewer may ask next
How would you rotate the array by exactly one step?
Use the same algorithm with k = 1. After normalization, reverse the whole array, reverse the first one element, and reverse the remaining n - 1 elements. Reversing a one-element range changes nothing, but keeping the same method is correct. The time complexity remains O(n), and the auxiliary space remains O(1).
What changes if k is larger than the array length?
Normalize k with k = k % n before performing the reversals. Rotating by n positions returns the array to the same order, so full rotations can be removed. For example, with length 7, rotating by 10 positions is the same as rotating by 3. The three-reversal algorithm does not otherwise change. The time complexity stays O(n), and the auxiliary space stays O(1).
3. How would you search in a rotated sorted array?CodingMediumMicrosoft
i Question Details
Search for a target in a rotated sorted array and explain how you keep the runtime logarithmic.
Short Interview Answer (30-60 seconds)
I would use binary search on the rotated array. At each step, I find the middle, check whether the left half or the right half is sorted, and then keep only the half that can still contain the target. That makes the search logarithmic because the range gets cut down by about half each time. The time is O(log n), and the extra space is O(1).
This question asks me to find a target in a list that was sorted first and then rotated, so the order wraps around in the middle. I still need to return the index of the target, or -1 if it is not there. The best idea is to look at the middle, find which half is already sorted, and then keep only the half that can still hold the target. Each step removes the part that cannot work, so the search stays fast and matches the diagram. java-developer-microsoft-coding…
Useful Questions to Ask the Interviewer
Are all numbers distinct, as shown in the diagram?
Should I return -1 if the target is not present?
How to Explain It in an Interview
1. Understand the input and output
The input is a rotated sorted array and a target value. The output is the index of the target, or -1 if the target is missing.
2. Use binary search on the rotated array
At every step, I check the middle element. One side around the middle is always sorted. I use that fact to decide which side can still contain the target.
3. Initialize the search range
I start with left at 0 and right at n - 1. This means the whole array is still in the search range. The middle index is computed from those bounds.
4. Walk through the example
Using [4, 5, 6, 7, 0, 1, 2] and target 0, the first middle is 3, so nums[mid] is 7. The left half [4, 5, 6, 7] is sorted, but 0 is not inside it. So I move to the right half. Then the next middle leads me to 0 at index 4, and I stop immediately.
5. Explain why the result is correct
The key rule is that the target, if it exists, always stays inside the current search range. Each step removes the half that cannot contain the target. That is why the answer is still correct after every move.
6. Explain the Java implementation
The code does the same checks as the diagram. It compares nums[mid] with target first. Then it checks which half is sorted. After that, it moves left or right to keep only the useful half. If nothing matches, it returns -1.
7. Explain complexity and edge cases
The search runs in O(log n) time because each step removes about half of the range. The extra space is O(1) because I only use a few variables. Important edge cases are a one-element array, an already sorted array, and a target that is not present.
Key Insight / Why This Solution Works
The key insight is that a rotated sorted array still has one sorted half at every middle position. I compare the left bound, middle, and right bound to find that sorted half. Then I check whether the target fits inside that half. If it does, I keep searching there. If it does not, I discard that half and search the other one. The invariant is simple: if the target exists, it always stays inside the current search range.
Code
publicclassMain {
publicstaticvoidmain(String[] args) {
int[] nums = { 4, 5, 6, 7, 0, 1, 2 };
inttarget=0;
Solutionsolution=newSolution();
intindex= solution.search(nums, target);
System.out.println(index); // Expected output: 4
}
}
classSolution {
publicintsearch(int[] nums, int target) {
intleft=0;
intright= nums.length - 1;
while (left <= right) {
// Midpoint keeps the search logarithmic.intmid= left + (right - left) / 2;
// If we found the target, stop immediately.if (nums[mid] == target) {
return mid;
}
// Check whether the left half is sorted.if (nums[left] <= nums[mid]) {
// The target must stay inside the sorted left half to search there.if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
// Otherwise the right half is sorted.// Keep the right half only when the target fits inside it.if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
// Return -1 when the target is not present.return -1;
}
}
Time & Space Complexity
We do not scan the whole array. Each loop removes about half of the remaining range. So the time is O(log n). We only keep a few index variables like left, right, and mid. So the extra space is O(1).
Where it is used
This pattern is useful when a sorted list has been rotated or shifted, but you still need fast search. It shows up in interview problems, index lookups, and systems that keep sorted data after a pivot move.
Why Interviewers Ask This
Interviewers want to see that I can adapt binary search to a rotated array. They are checking whether I can spot the sorted half, keep the target inside the search range, and update pointers correctly. They also want to see exact index handling, clean Java code, correct early return, and an accurate O(log n) time and O(1) space explanation.
Common interview mistakes
A common mistake is returning nums[mid] instead of the index mid. Another mistake is checking the wrong half and moving the pointers in the wrong direction. Some candidates forget that only one half is sorted at a time. Another error is turning the search into a full linear scan, which loses the logarithmic time. It is also easy to forget the early return when the target is found.
Interview tip
Say the invariant out loud: one half is always sorted, and you only keep the half that can still contain the target.
Interviewer may ask next
What changes if the array can contain duplicate values?
The sorted-half test can become unclear when nums[left] == nums[mid] == nums[right]. In that case, I may need to shrink both ends by one step. That keeps correctness, but the worst case can fall to O(n) because duplicates can hide which half is sorted.
What if I need to search many targets in the same rotated array?
I can first find the rotation pivot once, then run normal binary search on the two sorted parts as needed. That keeps each query fast. The preprocessing cost is O(log n), and each search is still O(log n). The tradeoff is a little extra setup before the repeated queries.
4. How would you merge multiple sorted arrays using a min-heap?CodingMediumMicrosoft
i Question Details
Merge k sorted arrays into one sorted sequence using a priority queue. Explain the data structure choice and the time complexity.
Short Interview Answer (30-60 seconds)
I would merge the arrays with a min-heap. I keep the smallest current item from each array in the heap, so the heap size never grows past k. Each time I remove the smallest value, I add it to the result and then push the next value from the same array. Because every array is already sorted, this always gives the next global smallest value. The total time is O(N log k), and the extra space is O(k).
This problem asks me to combine several already sorted arrays into one sorted array. I do not sort everything again at the end. I keep the smallest current number from each array in a small helper structure. That lets me choose the next smallest number quickly, then replace it with the next number from the same array. The helper structure is a min-heap, also called a priority queue. It fits well here because each array is already sorted, so the next smallest value in the whole input must be one of those front items.
Useful Questions to Ask the Interviewer
Should I handle empty arrays too?
Do you want only the merged values, or do you also want to know which array each value came from?
How to Explain It in an Interview
1. Understand the input and required output
The input is k sorted arrays. The output is one sorted list with all values from all arrays. The diagram uses:
A0 = [1, 4, 7, 10]
A1 = [2, 5, 8, 11]
A2 = [3, 6, 9, 12]
The final output is: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
2. Choose the algorithm and data structure
I use a min-heap, also called a priority queue. Each heap item stores:
value
arrayIndex
elementIndex
The key idea is simple. The heap always contains the smallest unmerged candidate from each array. Because each array is sorted, the smallest remaining value must be one of those heap items.
3. Initialize the state
I start by putting the first element of each non-empty array into the heap:
(1, 0, 0)
(2, 1, 0)
(3, 2, 0)
The result list starts empty. The invariant is: the heap holds the next best candidate from each array that still has work left.
4. Walk through the example
Now I repeat the same action:
Remove the smallest heap item.
Add its value to the result.
Push the next value from the same array, if one exists.
For the diagram example:
Remove 1, then push 4 from A0.
Remove 2, then push 5 from A1.
Remove 3, then push 6 from A2.
Remove 4, then push 7 from A0.
Remove 5, then push 8 from A1.
Remove 6, then push 9 from A2.
Remove 7, then push 10 from A0.
Remove 8, then push 11 from A1.
Remove 9, then push 12 from A2.
Remove 10, 11, and 12. After that, all arrays are done and the heap becomes empty.
The output grows in sorted order the whole time.
5. Explain why the result is correct
The heap always gives me the smallest current candidate. Since each array is sorted, the next number from the same array is the only new candidate that can matter after I remove one item. So the heap never misses a smaller value, and the result list is built in sorted order.
6. Explain the Java implementation
The Java code creates a small Node class with value, array index, and element index. It uses PriorityQueue, which is a min-heap in Java. The code:
inserts the first item from each non-empty array
repeatedly polls the smallest node
appends that value to the result
pushes the next item from the same array
stops when the heap is empty
7. Explain complexity and edge cases
If N is the total number of values and k is the number of arrays, each heap push and pop costs O(log k). We do that once per value, so the total time is O(N log k). The heap holds at most one item per array, so extra space is O(k).
Useful edge cases are:
an empty input list
empty arrays inside the input
arrays with different lengths
duplicate values
negative values or zero
Key Insight / Why This Solution Works
The heap always holds the smallest unmerged candidate from each array. Because each array is sorted, the smallest remaining value in the whole input must be one of those heap entries. So the heap top is always the correct next output value. After I remove that value, I push the next item from the same array. This keeps the invariant true until all arrays are empty.
Code
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
publicclassMain {
privatestaticfinalclassNode {
finalint value;
finalint arrayIndex;
finalint elementIndex;
Node(int value, int arrayIndex, int elementIndex) {
this.value = value;
this.arrayIndex = arrayIndex;
this.elementIndex = elementIndex;
}
}
publicstatic List<Integer> mergeSortedArrays(List<List<Integer>> arrays) {
List<Integer> result = newArrayList<>();
// Defensive fallback for null or empty input.if (arrays == null || arrays.isEmpty()) {
return result;
}
// Java PriorityQueue is a min-heap, so the smallest value comes out first.
PriorityQueue<Node> minHeap = newPriorityQueue<>((a, b) ->
Integer.compare(a.value, b.value)
);
// Put the first element from each non-empty array into the heap.// The heap then holds one candidate from each array.for (intarrayIndex=0; arrayIndex < arrays.size(); arrayIndex++) {
List<Integer> array = arrays.get(arrayIndex);
if (array != null && !array.isEmpty()) {
minHeap.offer(newNode(array.get(0), arrayIndex, 0));
}
}
// Repeatedly remove the smallest current value.// After removing it, push the next value from the same array.while (!minHeap.isEmpty()) {
Nodecurrent= minHeap.poll();
result.add(current.value);
intnextElementIndex= current.elementIndex + 1;
List<Integer> sourceArray = arrays.get(current.arrayIndex);
// If the same array still has values left, add the next one.if (nextElementIndex < sourceArray.size()) {
minHeap.offer(
newNode(
sourceArray.get(nextElementIndex),
current.arrayIndex,
nextElementIndex
)
);
}
}
return result;
}
publicstaticvoidmain(String[] args) {
// This example matches the diagram exactly.
List<List<Integer>> arrays = Arrays.asList(
Arrays.asList(1, 4, 7, 10),
Arrays.asList(2, 5, 8, 11),
Arrays.asList(3, 6, 9, 12)
);
List<Integer> merged = mergeSortedArrays(arrays);
// Expected output:// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
System.out.println(merged);
}
}
Time & Space Complexity
Let N be the total number of values across all arrays, and let k be the number of arrays. The heap never has more than k items. Each insert or remove takes O(log k) time. We do that once for each value, so the total time is O(N log k). The extra memory is O(k) for the heap, plus the output list.
Where it is used
This pattern is useful when several sorted streams must be merged. Common examples are log files, batch exports, search results, and any system that needs the next smallest item from many sorted sources.
Why Interviewers Ask This
Interviewers use this question to check whether you can recognize a classic heap-based merge pattern. They also want to see if you can keep track of state with array and element indices, handle duplicates correctly, and explain why the heap size stays bounded by k. Good answers also show clean Java code and correct complexity reasoning.
Common interview mistakes
A common mistake is to put every value into the heap at once. That works, but it loses the O(k) heap size and is not the intended approach here.
Another mistake is to store only the value in the heap. You also need the array index and element index so you know where the next value comes from.
A third mistake is to remove a value and forget to push the next value from the same array. That breaks the invariant and makes the output incomplete.
A fourth mistake is to use the wrong complexity. This is not O(N) time. It is O(N log k) because every heap operation costs O(log k).
A final mistake is to compare values from the wrong arrays when duplicate values appear.
Interview tip
When you explain it, keep repeating one sentence: “The heap stores one candidate from each array.” That makes the invariant easy to remember and easy to defend.
Interviewer may ask next
What changes if two arrays contain the same value and you want a stable tie-break?
I would keep the same min-heap approach. I would only change the comparator to break ties by arrayIndex and then elementIndex. The correctness stays the same, and the time is still O(N log k) with O(k) extra space.
What changes if I only need the first M merged values?
I would stop the loop after outputting M values. The heap logic stays the same. The time becomes O(M log k) instead of O(N log k), and the extra space stays O(k).
5. How would you implement concurrent structures and debug queue code?CodingHardMicrosoft
i Question Details
Implement an LRU cache, a custom hash map, and a quadtree, then reason about thread safety and a producer-consumer queue. Discuss concurrency concerns and how you would make the structures safe under concurrent access.
Short Interview Answer (30-60 seconds)
I would split the problem into four pieces. For the LRU cache, I use a HashMap plus a doubly linked list so get and put are O(1) expected. For the custom hash map, I use buckets with separate chaining. For the quadtree, I split space when a leaf gets full. For the queue, I use a lock and two conditions so put waits when full and take waits when empty. The cache and queue are O(1) expected, the hash map is average O(1), and the quadtree is average O(log n) with O(n) worst case. Extra space is O(n).
This question asks me to build a few storage pieces and explain how they work when more than one thread uses them. One piece keeps the newest item easy to reach. One piece keeps keys in buckets. One piece splits space into smaller parts for points. One piece lets one side add items and the other side remove items safely. The goal is to show the right state after each step, keep the order correct, and make the shared pieces safe. I would use the same sample values from the diagram so every section stays consistent.
Useful Questions to Ask the Interviewer
Should I assume the queue is bounded and blocking, as the diagram shows?
Should the custom hash map resize when the load factor gets too high?
Should I treat the quadtree as one point per leaf before it splits?
How to Explain It in an Interview
1. Understand the input and required output
The sample in the diagram uses an LRU capacity of 3, a hash map with 5 buckets, a quadtree with root capacity 1, and a queue with capacity 3. The goal is not one single trick. It is to show the right state for each structure after each operation.
2. Choose the right structure for each job
The LRU cache uses a HashMap for direct access and a doubly linked list for recency order. The custom hash map uses buckets and separate chaining so colliding keys stay reachable. The quadtree uses spatial splits so each point goes to the right area. The bounded queue uses a lock and two conditions so put waits when the queue is full and take waits when it is empty.
3. Initialize the state
Start with empty buckets, an empty cache list with dummy head and tail nodes, an empty quadtree root, and an empty bounded queue. That is the correct starting point because every later change is small and easy to trace.
4. Walk through the example
For the cache, insert 1, 2, and 3. Then read 1, which moves it to the front. Then insert 4, which evicts 2. The final order is [4, 1, 3]. For the hash map, keys 1, 6, 11, and 16 all land in bucket 1 when the bucket count is 5. For the quadtree, the inserted points make the root split and send points into the right child regions. For the queue, put 1, 2, and 3. Then take 1. Then put 4. Then take 2, 3, and 4. The taken order is 1, 2, 3, 4.
5. Explain why the result is correct
The key invariant is simple. The cache list always stays in most-recent to least-recent order. The hash map keeps each key in the right bucket chain. The quadtree keeps each point in the correct space region. The queue count always stays between 0 and capacity. Because these rules stay true after every operation, the final state is correct.
6. Explain the Java implementation
The Java code follows the same order as the diagram. The cache uses a map plus a doubly linked list. The map gives fast lookup. The list gives fast move-to-front and eviction. The custom hash map uses an array of buckets and linked entries. The quadtree inserts a point, splits when a leaf is full, and sends old points back down after the split. The bounded queue uses a circular buffer, a lock, and two conditions. put waits when the buffer is full. take waits when the buffer is empty. This matches the sample runner the prompt asks for.
7. Explain complexity and edge cases
The cache get and put operations are O(1). The custom hash map operations are average O(1), with a worse case of O(n) in one bucket if many keys collide. The quadtree insert and query operations are average O(log n), but they can fall to O(n) if many points crowd into one area. The queue put and take operations are amortized O(1). Relevant edge cases include duplicate keys, a full cache, an empty queue, repeated points, and very uneven point clusters.
Key Insight / Why This Solution Works
The simplest correct idea is to give each structure one strong invariant. The LRU cache uses the map for direct access and the doubly linked list for recency order. The custom hash map uses buckets and separate chaining so colliding keys are still easy to find. The quadtree keeps each leaf small and splits space when a region gets full. The bounded queue uses a lock and two conditions so put waits when full and take waits when empty. This is better than a plain list or shared array because it keeps the state fast to read and safe to update.
Code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
publicclassMain {
publicstaticvoidmain(String[] args)throws InterruptedException {
// ---- LRU cache demo from the diagram ----LRUCachelru=newLRUCache(3);
lru.put(1, 1);
lru.put(2, 2);
lru.put(3, 3);
lru.get(1); // Touch key 1 so it becomes most recent.
lru.put(4, 4); // This evicts key 2 from the tail.
System.out.println(
"LRU final order (MRU -> LRU): " + lru.keysFromMostToLeastRecent() + " (evicted: 2)"
);
// ---- Custom hash map demo from the diagram ----
MyHashMap<Integer, Integer> map = newMyHashMap<>(5);
map.put(1, 1);
map.put(6, 6);
map.put(11, 11);
map.put(16, 16);
System.out.println("HashMap buckets: " + map.bucketSnapshot());
// ---- Quadtree demo from the diagram ----QuadTreetree=newQuadTree(0, 0, 8, 8, 1);
QuadTree.Point[] points = {
newQuadTree.Point(1, 1),
newQuadTree.Point(2, 2),
newQuadTree.Point(6, 5),
newQuadTree.Point(7, 7),
newQuadTree.Point(2, 6),
newQuadTree.Point(6, 2),
};
for (QuadTree.Point point : points) {
tree.insert(point);
}
System.out.println(
"Quadtree root split: " +
tree.rootSplitOccurred() +
", points inserted: [(1, 1), (2, 2), (6, 5), (7, 7), (2, 6), (6, 2)]"
);
// ---- Bounded producer-consumer queue demo from the diagram ----
PCQueue<Integer> queue = newPCQueue<>(3);
queue.put(1);
queue.put(2);
queue.put(3);
List<Integer> takenOrder = newArrayList<>();
takenOrder.add(queue.take());
queue.put(4);
takenOrder.add(queue.take());
takenOrder.add(queue.take());
takenOrder.add(queue.take());
System.out.println("Queue output order: " + takenOrder);
}
// LRU cache: HashMap for O(1) access and a doubly linked list for recency.staticfinalclassLRUCache {
privatestaticfinalclassNode {
int key, value;
Node prev, next;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
privatefinalint capacity;
privatefinal Map<Integer, Node> map = newHashMap<>();
privatefinalNodehead=newNode(0, 0); // Most recent side.privatefinalNodetail=newNode(0, 0); // Least recent side.
LRUCache(int capacity) {
this.capacity = capacity;
head.next = tail;
tail.prev = head;
}
privatevoidaddFirst(Node node) {
// Insert right after head so this node becomes most recent.
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
privatevoidremove(Node node) {
// Unlink one node in O(1) time.
node.prev.next = node.next;
node.next.prev = node.prev;
}
private Node removeLast() {
// The node before tail is the least recent entry.if (tail.prev == head) {
returnnull;
}
Nodelru= tail.prev;
remove(lru);
return lru;
}
publicsynchronizedintget(int key) {
Nodenode= map.get(key);
if (node == null) {
return -1;
}
// A read is still an access, so refresh the recency.
remove(node);
addFirst(node);
return node.value;
}
publicsynchronizedvoidput(int key, int value) {
if (capacity <= 0) return;
Nodenode= map.get(key);
if (node != null) {
// Update the value and move the node to the front.
node.value = value;
remove(node);
addFirst(node);
return;
}
// Evict the least recent entry first when the cache is full.if (map.size() == capacity) {
Nodelru= removeLast();
if (lru != null) {
map.remove(lru.key);
}
}
Nodefresh=newNode(key, value);
addFirst(fresh);
map.put(key, fresh);
}
publicsynchronized List<Integer> keysFromMostToLeastRecent() {
List<Integer> keys = newArrayList<>();
for (Nodecur= head.next; cur != tail; cur = cur.next) {
keys.add(cur.key);
}
return keys;
}
}
// Custom hash map with separate chaining.staticfinalclassMyHashMap<K, V> {
privatestaticfinalclassEntry<K, V> {
final K key;
V value;
Entry<K, V> next;
Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
private Entry<K, V>[] buckets;
privateint size;
privateint threshold;
@SuppressWarnings("unchecked")
MyHashMap(int capacity) {
intn= Math.max(1, capacity);
buckets = (Entry<K, V>[]) newEntry[n];
threshold = Math.max(1, (int) Math.ceil(n * 0.75));
}
privateintindexFor(Object key, int bucketCount) {
return (key.hashCode() & 0x7fffffff) % bucketCount;
}
privatebooleaninsertIntoTable(Entry<K, V>[] table, K key, V value) {
intindex= indexFor(key, table.length);
Entry<K, V> cur = table[index];
if (cur == null) {
table[index] = newEntry<>(key, value);
returntrue;
}
while (true) {
if (Objects.equals(cur.key, key)) {
cur.value = value;
returnfalse;
}
if (cur.next == null) {
cur.next = newEntry<>(key, value);
returntrue;
}
cur = cur.next;
}
}
@SuppressWarnings("unchecked")privatevoidresize() {
Entry<K, V>[] oldBuckets = buckets;
Entry<K, V>[] newBuckets = (Entry<K, V>[]) newEntry[oldBuckets.length * 2];
for (Entry<K, V> bucket : oldBuckets) {
for (Entry<K, V> cur = bucket; cur != null; cur = cur.next) {
insertIntoTable(newBuckets, cur.key, cur.value);
}
}
buckets = newBuckets;
threshold = Math.max(1, (int) Math.ceil(newBuckets.length * 0.75));
}
publicsynchronizedvoidput(K key, V value) {
booleaninserted= insertIntoTable(buckets, key, value);
if (inserted) {
size++;
if (size > threshold) {
resize();
}
}
}
publicsynchronized V get(K key) {
intindex= indexFor(key, buckets.length);
for (Entry<K, V> cur = buckets[index]; cur != null; cur = cur.next) {
if (Objects.equals(cur.key, key)) {
return cur.value;
}
}
returnnull;
}
publicsynchronized String bucketSnapshot() {
StringBuildersb=newStringBuilder();
for (inti=0; i < buckets.length; i++) {
if (i > 0) sb.append(" ");
sb.append("b").append(i).append(": [");
booleanfirst=true;
for (Entry<K, V> cur = buckets[i]; cur != null; cur = cur.next) {
if (!first) sb.append(", ");
sb.append(cur.key);
first = false;
}
sb.append("]");
}
return sb.toString();
}
}
// Quadtree: split space into four regions when a leaf gets full.staticfinalclassQuadTree {
staticfinalclassPoint {
finalint x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
privatestaticfinalclassNode {
finalint x, y, width, height, capacity;
final List<Point> points = newArrayList<>();
boolean divided;
Node nw, ne, sw, se;
Node(int x, int y, int width, int height, int capacity) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.capacity = capacity;
}
}
privatefinal Node root;
QuadTree(int x, int y, int width, int height, int capacity) {
this.root = newNode(x, y, width, height, Math.max(1, capacity));
}
publicsynchronizedbooleaninsert(Point point) {
return insert(root, point);
}
privatebooleaninsert(Node node, Point point) {
if (!contains(node, point)) returnfalse;
if (!node.divided && node.points.size() < node.capacity) {
node.points.add(point);
returntrue;
}
if (!node.divided) {
subdivide(node);
}
return (
insert(node.nw, point) ||
insert(node.ne, point) ||
insert(node.sw, point) ||
insert(node.se, point)
);
}
privatebooleancontains(Node node, Point point) {
return (
point.x >= node.x &&
point.x < node.x + node.width &&
point.y >= node.y &&
point.y < node.y + node.height
);
}
privatevoidsubdivide(Node node) {
if (node.width <= 1 && node.height <= 1) return;
inthw= node.width / 2;
inthh= node.height / 2;
node.nw = newNode(node.x, node.y, hw, hh, node.capacity);
node.ne = newNode(node.x + hw, node.y, node.width - hw, hh, node.capacity);
node.sw = newNode(node.x, node.y + hh, hw, node.height - hh, node.capacity);
node.se = newNode(
node.x + hw,
node.y + hh,
node.width - hw,
node.height - hh,
node.capacity
);
node.divided = true;
// Move old points into the new children after the split.
List<Point> old = newArrayList<>(node.points);
node.points.clear();
for (Point p : old) {
insert(node, p);
}
}
publicsynchronizedbooleanrootSplitOccurred() {
return root.divided;
}
}
// Bounded producer-consumer queue with one lock and two conditions.staticfinalclassPCQueue<E> {
privatefinal Object[] buffer;
privateint head;
privateint tail;
privateint count;
privatefinalReentrantLocklock=newReentrantLock();
privatefinalConditionnotFull= lock.newCondition();
privatefinalConditionnotEmpty= lock.newCondition();
PCQueue(int capacity) {
if (capacity <= 0) {
thrownewIllegalArgumentException("capacity must be positive");
}
buffer = newObject[capacity];
}
publicvoidput(E item)throws InterruptedException {
lock.lock();
try {
// Wait in a loop because another thread may wake up first.while (count == buffer.length) {
notFull.await();
}
buffer[tail] = item;
tail = (tail + 1) % buffer.length;
count++;
// Signal that consumers can make progress now.
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")public E take()throws InterruptedException {
lock.lock();
try {
// Wait in a loop because another thread may wake up first.while (count == 0) {
notEmpty.await();
}
Eitem= (E) buffer[head];
buffer[head] = null;
head = (head + 1) % buffer.length;
count--;
// Signal that producers can make progress now.
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
}
Time & Space Complexity
The cache get and put operations are O(1) expected because the HashMap gives direct access and the linked list moves nodes in constant time. The custom hash map is average O(1), and one bad bucket can make it O(n) in the worst case. The quadtree insert and query operations are average O(log n), but they can fall to O(n) if many points land in one region. The bounded queue put and take operations are amortized O(1). Extra space is O(n) for stored nodes, buckets, points, and queue slots.
Where it is used
These patterns show up in browser caches, in-memory indexes, spatial search in maps and games, and producer-consumer pipelines such as log processing or worker queues.
Why Interviewers Ask This
Interviewers want to see whether I can pick the right structure for each job, keep shared state correct, and explain tradeoffs in simple words. They also want to know that I can reason about recency, collisions, spatial splits, blocking behavior, and thread safety without mixing them up. This question checks Java basics, careful state updates, and honest complexity talk instead of overclaiming worst-case guarantees.
Common interview mistakes
A common mistake is to use a plain list for the cache and scan it every time. That makes the cache too slow. Another mistake is to forget to move a touched cache entry to the front. That breaks recency. A third mistake is to use if instead of while around queue waits. That can fail when a thread wakes up at the wrong time. A fourth mistake is to split the quadtree but forget to push the old point back down into the new child region.
Interview tip
I would name the invariant first, then show one operation that changes it. For example, I would say the cache list always goes from most recent to least recent, and then show how get and put keep that order.
Interviewer may ask next
What changes if many threads call the queue at the same time?
I would keep the same blocking rule, but I would expect more contention. The simplest safe choice is one lock with two conditions, which keeps the count and the buffer consistent. If throughput matters more, I would move to a standard library BlockingQueue or a finer-grained design. The invariant stays the same. The tradeoff is simpler correctness versus higher parallelism.
What changes if the quadtree gets many points in one small area?
The tree can become deep in that area, so inserts get slower. I can reduce that risk by raising the leaf capacity, stopping at a minimum cell size, or switching to another spatial index if the data is very clustered. Correctness stays the same because each point still belongs to one region. The tradeoff is memory and balance versus update speed.
6. How would you return the right side view of a binary tree?CodingEasyMicrosoft
i Question Details
Return the visible nodes from the right side of a binary tree using either DFS or BFS. Explain how you choose the first node seen at each level.
Short Interview Answer (30-60 seconds)
I would use BFS with a queue and process the tree one level at a time. For each level, I save the current queue size. I process the right child before the left child, so the first node removed from the queue at that level is the node visible from the right side. I add only that first value to the result. Each node is visited once, so time is O(n). The queue uses O(w) auxiliary space, where w is the maximum tree width.
The input is the root of a binary tree. We need to return the values that a person would see when looking at the tree from its right side, from the top level to the bottom level. I process the tree one level at a time. For each node, I add its right child before its left child. This makes the first node processed on the next level the value visible from the right side. The final list contains one visible value for each level.
Useful Questions to Ask the Interviewer
Should an empty tree return an empty list?
Do you want the visible node values from top to bottom?
How to Explain It in an Interview
1. Understand the input and required output
The input is the root node of a binary tree. The output is a list of node values. We need one value from each level. That value must be the node visible from the right side.
For the example tree, the level values are: Level 0: [1] Level 1: [2, 3] Level 2: [5, 4]
The visible result is [1, 3, 4].
2. Choose BFS and a queue
I use breadth-first search, or BFS. BFS processes the tree one level at a time. A queue stores the nodes that still need to be processed.
The important rule is that I enqueue the right child before the left child. Because of this, the queue holds each next level in right-to-left processing order. The first node removed for a level is therefore the node visible from the right side.
3. Initialize the state
If root is null, I return an empty result list.
Otherwise, I create an empty result list and a queue. I put the root node into the queue.
For the example, the initial state is: queue = [1] result = []
At the start of every level, I save queue.size(). This tells me exactly how many nodes belong to that level.
4. Walk through the example
At level 0, the queue is [1]. The level size is 1. Node 1 is the first node in this level, so i == 0. I add 1 to the result. I enqueue its right child 3 first, then its left child 2. The queue becomes [3, 2]. The result is [1].
At level 1, the queue starts as [3, 2]. Node 3 is processed first. Because i == 0, I add 3. Node 3 has right child 4, so I enqueue 4. The queue is now [2, 4]. Next I process node 2. It is not the first node of the level, so I do not add it. Node 2 has right child 5, so I enqueue 5. The queue for the next level becomes [4, 5]. The result is [1, 3].
At level 2, the queue starts as [4, 5]. Node 4 is first, so I add 4. It has no children. Then I process node 5, but I do not add it because it is not first in the level. The queue becomes empty. The final result is [1, 3, 4].
5. Explain why the result is correct
At the start of each level, the queue contains that level's nodes in right-to-left processing order. Therefore, the first node removed from the queue is the rightmost node for that level. I add only that first node. Repeating this for every level gives exactly the right-side view from top to bottom.
6. Explain the Java implementation
The outer while loop runs while the queue has nodes. At the start of each level, levelSize stores the number of nodes currently in the queue. The for loop processes exactly those nodes. When i == 0, the current node is the first node of that level, so its value is added to the result. The code then adds node.right before node.left. After all levels are processed, it returns the result.
7. Explain complexity and edge cases
Every node is removed from the queue and processed once, so the time complexity is O(n), where n is the number of nodes. The queue can hold up to one full level, so auxiliary space is O(w), where w is the maximum width of the tree. In the worst case, w can be O(n).
Important edge cases are an empty tree, a single-node tree, a left-skewed tree, and a right-skewed tree. An empty tree returns []. A single node returns [root.val]. In either skewed tree, every level still contributes one visible node.
Key Insight / Why This Solution Works
The key idea is to process the tree level by level with BFS while keeping each level in right-to-left processing order. A queue is a good fit because it naturally separates one level from the next. Before processing a level, I record its current size. I enqueue each node's right child before its left child. The invariant is: at the start of each level, the queue contains that level's nodes in right-to-left order. Therefore, the first node removed from the queue is the rightmost visible node, and only that value is added to the result.
Code
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
publicclassMain {
staticclassTreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
publicstatic List<Integer> rightSideView(TreeNode root) {
// Store one visible node value for each tree level.
List<Integer> result = newArrayList<>();
// An empty tree has no nodes visible from the right side.if (root == null) {
return result;
}
// The queue lets us process the tree one complete level at a time.
Queue<TreeNode> queue = newArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
// Save the current level size before adding nodes from the next level.intlevelSize= queue.size();
// Process exactly the nodes that belong to this level.for (inti=0; i < levelSize; i++) {
TreeNodenode= queue.poll();
// Right-first queue order makes the first node the visible node.if (i == 0) {
result.add(node.val);
}
// Add the right child first so the next level is processed right to left.if (node.right != null) {
queue.offer(node.right);
}
// Add the left child after the right child.if (node.left != null) {
queue.offer(node.left);
}
}
}
// The result now contains the right-side view from top to bottom.return result;
}
publicstaticvoidmain(String[] args) {
// Build the diagram example: [1, 2, 3, null, 5, null, 4].TreeNoderoot=newTreeNode(1);
root.left = newTreeNode(2);
root.right = newTreeNode(3);
root.left.right = newTreeNode(5);
root.right.right = newTreeNode(4);
// Expected output: [1, 3, 4].
System.out.println(rightSideView(root));
}
}
Time & Space Complexity
The time complexity is O(n), where n is the number of nodes in the tree. Every node enters the queue once, leaves the queue once, and is processed once. The auxiliary space is O(w), where w is the maximum number of nodes on any one level of the tree. The queue may need to hold that whole level. In the worst case, the maximum width can grow to O(n).
Where it is used
This level-order traversal pattern is useful when software needs to process hierarchical data one depth at a time. Similar queue-based tree traversal is used for level summaries, displaying tree layers, finding values by depth, and processing organization or category hierarchies where nodes on the same level belong to the same stage.
Why Interviewers Ask This
This problem checks whether you can recognize level-order tree traversal and use a queue correctly. The interviewer can see whether you understand level boundaries, traversal order, and a simple invariant. It also tests whether your Java implementation matches your explanation, especially the right-before-left enqueue order and the i == 0 condition. Finally, it checks whether you can explain O(n) time, O(w) queue space, and basic tree edge cases accurately.
Common interview mistakes
A common mistake is enqueueing the left child before the right child while still adding the first node of each level. That would produce the left-side view instead. Another mistake is forgetting to save levelSize before processing the level. If the loop uses the changing queue size, nodes from the next level can be mixed into the current level. A candidate may also add every node instead of only the node where i == 0. Another mistake is claiming O(1) extra space even though the queue can grow with the tree width. Finally, remember to handle a null root before adding it to ArrayDeque.
Interview tip
State the invariant before writing the loop: because I enqueue right before left, the first node processed at every level is the rightmost visible node. Then make the code follow that statement directly with levelSize and the i == 0 check.
Interviewer may ask next
What would change if you enqueued the left child before the right child?
Then the queue would process each level from left to right, so the first node would no longer be the right-side node. To keep left-first enqueue order, I could instead add the last node of each level by checking i == levelSize - 1. The correctness idea would become: the last processed node of a left-to-right level is the rightmost node. Time would still be O(n), and auxiliary space would still be O(w). The tradeoff is only which processing order and condition I use.
What is the maximum extra space used by this BFS solution?
The queue stores nodes from a tree level, so the auxiliary space is O(w), where w is the maximum width of the tree. A skewed tree has width 1, so the queue stays small. A very wide tree can have O(n) nodes on one level, so the worst-case auxiliary space is O(n). The algorithm and correctness do not change.
7. How would you find the lowest common ancestor in a tree?CodingMediumMicrosoft
i Question Details
Find the lowest common ancestor in a BST and explain how the approach changes for a general binary tree.
Short Interview Answer (30-60 seconds)
I’d use the BST ordering to walk down from the root. If both target values are smaller than the current node, I move left. If both are larger, I move right. Otherwise, the paths split or the current node matches one target, so I return the current node as the LCA. This takes O(h) time and O(1) extra space. For a general binary tree, I would recursively search both subtrees, which takes O(n) time and O(h) recursion-stack space.
We need to return the lowest node whose subtree contains both target nodes. A target node can also be the answer itself. In a BST, smaller values are on the left and larger values are on the right. That ordering lets us choose only one subtree when both targets are on the same side. We stop when the targets split across the current node, or when the current node matches one target. In the shown example, we start at 6, move left to 2, and return node 2.
Useful Questions to Ask the Interviewer
Can I assume both target nodes are present in the tree?
Should I return the node reference rather than only its value?
How to Explain It in an Interview
1. Understand the input and required output
The input is the root of a binary search tree and two target nodes, p and q. We must return the lowest node whose subtree contains both targets. A node can be an ancestor of itself. In the diagram, root is 6, p is node 2, q is node 4, and the returned LCA is node 2.
2. Use the BST ordering
A BST keeps smaller values on the left and larger values on the right. If both p.val and q.val are smaller than current.val, both targets are in the left subtree, so we move left. If both are larger, both targets are in the right subtree, so we move right. Otherwise, the paths split at current or current matches one target, so current is the LCA.
3. Initialize the state
Set current to root. In the example, current starts at node 6. The invariant is that current remains an ancestor candidate for both targets. We move to one child only when both targets are known to be on that same side.
4. Walk through the example
At current = 6, p = 2 and q = 4 are both less than 6. The algorithm moves left, so current becomes 2. At current = 2, p.val equals current.val. Therefore, neither same-side condition holds. The algorithm returns current and stops. The returned node is 2.
5. Explain why the result is correct
If both targets are strictly on the same side of current, their lowest common ancestor must also be on that side, so moving there is safe. The first node where the targets are not both strictly on one side is either their split point or one of the targets itself. That node is the lowest node whose subtree contains both targets. Here, node 2 is an ancestor of itself and node 4.
6. Explain the Java implementation and the general-tree change
The Java code keeps one current pointer and uses a loop. It compares p.val and q.val with current.val, moves left or right only when both targets are on the same side, and otherwise returns current. For a general binary tree, value ordering is unavailable. Use recursive postorder search instead. If root is null, p, or q, return root. Search left and right. If both calls return non-null nodes, root is the LCA. Otherwise, return the non-null side.
7. Explain complexity and edge cases
For the BST solution, time is O(h), where h is the tree height, and auxiliary space is O(1). A balanced BST gives O(log n) time, while a skewed BST can take O(n). Relevant cases include p being an ancestor of q, q being an ancestor of p, both targets starting in the same subtree, and a skewed tree. For the general binary tree approach, time is O(n) and recursion-stack space is O(h).
Key Insight / Why This Solution Works
The key insight is to use the BST ordering instead of searching both subtrees. At each current node, compare both target values with current.val. If both are smaller, move left. If both are larger, move right. Otherwise, return current because the target paths split there or current matches one target. The central invariant is that current remains an ancestor candidate for both targets. This gives a single root-to-answer traversal. In a general binary tree, this ordering rule does not exist, so recursive postorder search of both subtrees is needed.
Code
publicclassMain {
staticclassTreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
publicstatic TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// Start at the root. Current remains an ancestor candidate for both targets.TreeNodecurrent= root;
while (current != null) {
// If both targets are smaller, BST ordering says their LCA is in the left subtree.if (p.val < current.val && q.val < current.val) {
current = current.left;
}
// If both targets are larger, their LCA is in the right subtree.elseif (p.val > current.val && q.val > current.val) {
current = current.right;
}
// Otherwise, the paths split here or current matches p or q.// In either case, current is the lowest common ancestor.else {
return current;
}
}
// Defensive fallback; the stated problem guarantees a solution.returnnull;
}
publicstaticvoidmain(String[] args) {
// Build the exact BST from the diagram.TreeNoderoot=newTreeNode(6);
root.left = newTreeNode(2);
root.right = newTreeNode(8);
root.left.left = newTreeNode(0);
root.left.right = newTreeNode(4);
root.left.right.left = newTreeNode(3);
root.left.right.right = newTreeNode(5);
root.right.left = newTreeNode(7);
root.right.right = newTreeNode(9);
// Use the exact target nodes from the diagram: p = 2 and q = 4.TreeNodep= root.left;
TreeNodeq= root.left.right;
// Run the BST LCA algorithm. The expected returned node has value 2.TreeNodelca= lowestCommonAncestor(root, p, q);
// Print the diagram's expected result.
System.out.println(lca.val);
}
}
Time & Space Complexity
For the BST solution, the algorithm follows only one path down the tree. If the tree height is h, the time is O(h). In a balanced BST, h is O(log n). In a skewed BST, h can be O(n). The iterative solution keeps only one current pointer, so auxiliary space is O(1). For the general binary tree method shown in the diagram, we may visit every node, so time is O(n). Its recursion stack can grow to the tree height, so auxiliary space is O(h).
Where it is used
This pattern is useful when software stores ordered hierarchical data in a binary search tree and needs the first common point on two search paths. The general-tree version is useful for ordinary tree hierarchies where node values do not provide a left-versus-right ordering shortcut.
Why Interviewers Ask This
This problem tests whether you recognize and use the ordering property of a BST instead of doing unnecessary traversal. It also checks whether you can maintain a clear invariant, handle the case where one target is an ancestor of the other, write correct Java tree logic, and explain O(h) time accurately. The general binary tree follow-up tests whether you understand why the BST shortcut no longer works when value ordering is removed and why both subtrees may need to be searched.
Common interview mistakes
A common mistake is searching both subtrees even though BST ordering lets us choose one side. Another is moving left when only one target is smaller, or moving right when only one target is larger. Both targets must be strictly on the same side before moving. Candidates can also forget that one target may itself be the LCA, as node 2 is for nodes 2 and 4. Another mistake is claiming the BST solution always takes O(log n); a skewed BST can require O(n) time.
Interview tip
Say the three-way rule before writing code: both smaller means go left, both larger means go right, otherwise return current. Then trace 6 to 2 to show why a target node can itself be the LCA.
Interviewer may ask next
How does the solution change for a general binary tree that is not a BST?
We cannot use value comparisons to choose one subtree. Use recursive postorder search. If root is null, p, or q, return root. Recursively search the left and right children. If both calls return non-null nodes, the current root is the LCA. Otherwise, return whichever side is non-null. This is correct because a node with one target found on each side is their first common ancestor. Time is O(n), and recursion-stack space is O(h). The tradeoff is that we may need to search both subtrees.
What happens if the BST is highly skewed?
The algorithm stays correct because the same-side invariant does not depend on the tree being balanced. However, the height h can become n, so O(h) time becomes O(n). The iterative solution still uses O(1) auxiliary space because it keeps only the current pointer. The tradeoff is slower search compared with a balanced BST, where the height is O(log n).
8. How would you find the shortest path in a weighted grid with constraints?CodingHardMicrosoft
i Question Details
Find the shortest path in a weighted grid subject to additional constraints, and explain the algorithm, edge cases, and trade-offs.
Short Interview Answer (30-60 seconds)
I would use Dijkstra’s algorithm with a min-heap and a dist table. I always pop the cell with the smallest total cost, then try its four neighbors and relax a neighbor only when I find a cheaper path. Because every move cost is non-negative, the first time I pop the goal, that cost is final. The time is O(mn log(mn)) and the extra space is O(mn).
This problem asks me to move from the top-left square to the bottom-right square in a grid. Every square has a cost, and some squares may be blocked. I need the cheapest total cost to reach the end, or -1 if there is no safe path. The best way is to always look at the cheapest place I know about next. That fits this diagram because every cost is non-negative, so once the goal is picked first, its cost is already final.
Useful Questions to Ask the Interviewer
Are blocked cells marked with -1, and should I return -1 if the start or goal is blocked?
Should I count the cost of the starting square in the total?
How to Explain It in an Interview
1. Understand the input and output
The input is a grid of non-negative costs. Some cells may be blocked. The output is the minimum total cost from (0, 0) to (m - 1, n - 1), or -1 if there is no path.
2. Choose Dijkstra with a min-heap
I use Dijkstra’s algorithm because all costs are non-negative. I store each state as (cost, row, col). The heap always gives me the next cheapest state. The dist table stores the best cost I have found so far for each cell.
3. Initialize the state
I set every dist cell to INF. Then I set dist[0][0] to the start cost and push the start state into the min-heap. This is the correct start because the path begins at the top-left cell.
4. Walk through the example
The diagram uses one 4 x 4 grid. The start is (0, 0). The goal is (3, 3). I pop the cheapest state first. I check four directions. I skip blocked cells and cells outside the grid. When a new path is cheaper, I update dist and push the new state.
5. Explain why the result is correct
The key rule is simple. The heap always gives the smallest current cost first. Because costs never go down, the first time I pop the goal, that cost cannot be beaten later. That is why I can stop early.
6. Explain the Java implementation
The Java code creates the same 4 x 4 example grid, builds dist, and uses a PriorityQueue for the heap. It skips stale heap entries. It stops right away when it pops the goal. If the heap becomes empty first, it returns -1.
7. Explain complexity and edge cases
There are m * n cells in the grid. Each useful relaxation may push a state into the heap. That gives O(mn log(mn)) time and O(mn) extra space. The important edge cases are a 1 x 1 grid, zero-cost cells, blocked start or goal cells, and no reachable path.
Key Insight / Why This Solution Works
I model each cell as a state. Dijkstra’s algorithm fits because all cell costs are non-negative. The dist table stores the best cost found so far for each cell. The min-heap always exposes the state with the smallest current cost. That is the central invariant. When I pop the goal from the heap, that is the final minimum cost, because no later path can be cheaper.
Code
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
publicclassMain {
publicstaticvoidmain(String[] args) {
// This is the exact example shown in the diagram.int[][] grid = { { 1, 3, 1, 4 }, { 2, 1, 5, 1 }, { 4, 1, 1, 3 }, { 2, 2, 1, 1 } };
// Print the minimum cost for the example path.
System.out.println(newSolution().minCost(grid));
}
}
classSolution {
privatestaticfinalintINF= Integer.MAX_VALUE;
privatestaticfinalint[][] DIRS = {
{ 1, 0 }, // down
{ -1, 0 }, // up
{ 0, 1 }, // right
{ 0, -1 }, // left
};
publicintminCost(int[][] grid) {
intm= grid.length;
intn= grid[0].length;
// If the start or goal is blocked, there is no valid path.if (grid[0][0] == -1 || grid[m - 1][n - 1] == -1) {
return -1;
}
// dist[r][c] stores the best cost found so far for cell (r, c).int[][] dist = newint[m][n];
for (int[] row : dist) {
Arrays.fill(row, INF);
}
// Min-heap ordered by the total path cost so far.
PriorityQueue<int[]> pq = newPriorityQueue<>(Comparator.comparingInt(a -> a[0]));
// The path starts at the top-left cell, so we include its own cost.
dist[0][0] = grid[0][0];
pq.offer(newint[] { grid[0][0], 0, 0 });
while (!pq.isEmpty()) {
int[] cur = pq.poll();
intcost= cur[0];
intr= cur[1];
intc= cur[2];
// Skip old heap entries that are no longer the best known cost.if (cost > dist[r][c]) {
continue;
}
// The first time we pop the goal, Dijkstra guarantees it is optimal.if (r == m - 1 && c == n - 1) {
return cost;
}
// Try the four possible moves from the current cell.for (int[] d : DIRS) {
intnr= r + d[0];
intnc= c + d[1];
// Stay inside the grid.if (nr < 0 || nr >= m || nc < 0 || nc >= n) {
continue;
}
// Skip blocked cells.if (grid[nr][nc] == -1) {
continue;
}
intnewCost= cost + grid[nr][nc];
// Relax the neighbor only when we found a cheaper path.if (newCost < dist[nr][nc]) {
dist[nr][nc] = newCost;
pq.offer(newint[] { newCost, nr, nc });
}
}
}
// Defensive fallback; the diagram says return -1 if the goal is unreachable.return -1;
}
}
Time & Space Complexity
There are m * n cells in the grid. Dijkstra may put a cell into the heap more than once, but each heap push and pop costs log(mn). So the total time is O(mn log(mn)). The dist table uses O(mn) space, and the heap can also hold O(mn) states, so the extra space is O(mn).
Where it is used
This pattern is useful in maps, robot movement, games, and route planning when moving into each cell has a cost. It also helps any time we need the cheapest path in a graph with non-negative weights.
Why Interviewers Ask This
Interviewers want to see whether I can turn the grid into states, pick Dijkstra for non-negative costs, and keep the right invariant. They also check whether I handle blocked cells, stale heap entries, and early stop correctly. This question tests careful thinking, correct Java code, and honest complexity reasoning.
Common interview mistakes
A common mistake is to use BFS. BFS does not work here because the grid has weights, not equal steps. Another mistake is to forget that the start cell cost counts in the total. A third mistake is to skip the stale-entry check, which can make the heap process old paths again. Another easy mistake is to ignore blocked cells or to return a cost before the goal is actually popped from the heap.
Interview tip
Say the invariant out loud: the heap always gives the cheapest known cell first, so the first popped goal is final.
Interviewer may ask next
How would you return the actual path, not just the minimum cost?
I would store a parent cell for every relaxation. When I pop the goal, I would walk back through the parents to rebuild the path. The time stays O(mn log(mn)) and the space stays O(m*n), plus the parent table.
What changes if some cells are blocked with -1?
The Dijkstra logic stays the same. I only skip cells with value -1. If the start or goal is blocked, I return -1. The time and space complexity do not change.
9. How would you reverse a list in-place?CodingEasyMicrosoft
i Question Details
Implement an in-place list reversal without allocating another list. Cover the algorithm, edge cases, and why the extra space stays O(1).
Short Interview Answer (30-60 seconds)
I would use two indices, one at the start and one at the end of the list. While left is smaller than right, I swap the two values and move both indices toward the center. Each swap places two elements into their final reversed positions. When the indices meet or cross, the same list is fully reversed. For an array-backed list such as ArrayList, this takes O(n) time and uses O(1) auxiliary space.
The task is to change the existing list so its values appear in the opposite order. We must not create another list for the reversed result. I would start with one position at each end of the list, exchange those two values, and then move both positions toward the center. Each exchange puts two values directly into their correct reversed positions. Because the original list is changed directly and only a few temporary variables are needed, this method satisfies the in-place requirement.
Useful Questions to Ask the Interviewer
Can I assume the input list is mutable and supports indexed get and set operations?
Can I assume efficient indexed access, such as an ArrayList, when discussing the O(n) time complexity?
How to Explain It in an Interview
1. Understand the input and required output
The input is one mutable list. The goal is to reverse that same list without allocating another list for the answer. In the diagram, the example starts as [10, 20, 30, 40, 50]. After the method finishes, that same list contains [50, 40, 30, 20, 10]. The method does not need to return a new list because it changes the input list directly.
2. Choose the two-pointer approach
I use two indices called left and right. left starts at index
right starts at list.size() -
The central invariant is that every element outside the current [left, right] window is already in its final reversed position. Each swap makes that completed area larger.
3. Initialize the state
For [10, 20, 30, 40, 50], left starts at 0 and right starts at 4. The value at index 0 is 10. The value at index 4 is 50. The loop continues while left < right. This means there is still a pair of positions that needs to be exchanged.
4. Walk through the example
Step 1 starts with [10, 20, 30, 40, 50]. left is 0 and right is 4. Save the value 10 in temp. Put 50 into index 0. Then put temp, which contains 10, into index 4. The list becomes [50, 20, 30, 40, 10]. Then left becomes 1 and right becomes 3.
Step 2 starts with [50, 20, 30, 40, 10]. left is 1 and right is 3. Save 20 in temp. Put 40 into index 1. Then put 20 into index 3. The list becomes [50, 40, 30, 20, 10]. Then left becomes 2 and right becomes 2.
Now left == right, so the condition left < right is false. The loop stops. The middle value 30 stays at index 2 because it is already in its correct reversed position.
5. Explain why the result is correct
After each swap, the elements at the current left and right positions are placed into their final reversed positions. Then the unfinished [left, right] window becomes smaller. Because the two indices keep moving toward the center, they eventually meet or cross. At that point, every position contains the value that belongs there in the reversed list.
6. Explain the Java implementation
The method keeps three pieces of temporary state: left, right, and temp. temp saves the current left value before that position is overwritten. The right value is written into the left position, and then the saved value is written into the right position. After the swap, left is incremented and right is decremented. The method returns void because the supplied List object has already been modified.
7. Explain complexity and edge cases
For an array or ArrayList, the algorithm takes O(n) time. It performs at most n/2 swaps, and indexed get and set operations are constant time. Auxiliary space is O(1) because it stores only left, right, and temp. An empty list performs no swaps. A one-element list is already reversed. An even-length list swaps all elements in pairs. An odd-length list leaves the middle element in place. Duplicate and negative values work correctly because the algorithm only exchanges positions. On LinkedList, the same indexed code is slower because get and set by index require traversal.
Key Insight / Why This Solution Works
The key idea is to reverse the list from the outside toward the center. left starts at the first index and right starts at the last index. The values at those positions are swapped, and then left moves right while right moves left. The invariant is that all elements outside the current [left, right] window are already in their final reversed positions. When left reaches or passes right, no unreversed pair remains. This method is a good fit because it changes the original list directly and uses only a constant amount of extra memory.
Code
import java.util.ArrayList;
import java.util.List;
publicclassMain {
publicstatic <T> voidreverseInPlace(List<T> list) {
// Start one index at the first element and one at the last element.intleft=0;
intright= list.size() - 1;
// Continue while there is still an outer pair that needs to be reversed.while (left < right) {
// Save the left value before overwriting its position.Ttemp= list.get(left);
// Put the current right value into its final position on the left.
list.set(left, list.get(right));
// Put the saved left value into its final position on the right.
list.set(right, temp);
// Shrink the unreversed window from both sides.
left++;
right--;
}
}
publicstaticvoidmain(String[] args) {
// Create the same mutable example list shown in the diagram.
List<Integer> list = newArrayList<>(List.of(10, 20, 30, 40, 50));
// Reverse the existing list object instead of creating a reversed result list.
reverseInPlace(list);
// The final list must be [50, 40, 30, 20, 10].
System.out.println(list);
}
}
Time & Space Complexity
For an array or ArrayList, the time complexity is O(n). The method performs at most n/2 swaps, and each indexed read or write takes O(1) time. Auxiliary space is O(1). Auxiliary space means extra memory used by the algorithm. The amount of extra memory does not grow with n because the method stores only left, right, and one temporary value. For a LinkedList, this exact index-based implementation can take O(n^2) time because each indexed get or set may require walking through part of the list.
Where it is used
This two-pointer pattern is useful when data can be changed directly from both ends. It is commonly used for reversing arrays or array-backed lists, swapping symmetric elements, checking structures from both ends, and other problems where a left and right boundary move toward the center. It is especially useful when avoiding an extra copy is important.
Why Interviewers Ask This
This question checks whether you recognize a simple two-pointer pattern and can modify data safely without extra storage. It tests whether you save a value before overwriting it, move both indices correctly, and use the right stopping condition. The interviewer can also evaluate whether you understand the invariant, handle empty and odd-length lists, write valid generic Java code, and explain why auxiliary space is O(1). It also gives you a chance to distinguish ArrayList indexed access from LinkedList indexed access.
Common interview mistakes
One common mistake is overwriting the left value before saving it in temp. That loses the value needed for the right position. Another mistake is forgetting to move both left and right after a swap, which can cause an infinite loop. Candidates may also create a second list, which breaks the in-place requirement. Using left <= right is unnecessary because the middle element does not need to swap with itself. Another mistake is claiming the indexed implementation is O(n) for every List type. That claim is correct for ArrayList, but not for LinkedList.
Interview tip
State the invariant while you code: everything outside the current [left, right] window is already in its final reversed position. Then each swap and pointer movement has a clear reason.
Interviewer may ask next
What changes if the list has an even number of elements?
The algorithm does not need to change. Every element belongs to a symmetric pair, so the pointers keep swapping pairs until they cross. For example, [10, 20, 30, 40] becomes [40, 30, 20, 10]. For an ArrayList, the time remains O(n) and the auxiliary space remains O(1). The only difference from an odd-length list is that there is no single middle element left unchanged.
What changes if the input is a LinkedList instead of an ArrayList?
The exact index-based code still produces the correct reversed order, but its performance changes. LinkedList.get(index) and LinkedList.set(index, value) require traversal, so repeatedly using them can make this implementation O(n^2) time. The auxiliary space is still O(1). If linear time is required for a LinkedList, I would keep the same outside-to-inside reversal idea but use bidirectional iterators instead of repeated indexed access. The tradeoff is more complicated code.
10. How would you convert a spreadsheet column label to a number?CodingEasyMicrosoft
i Question Details
Convert a spreadsheet-style column label such as A, Z, or AA to its 1-indexed column number. Explain the base-26 interpretation and complexity.
Short Interview Answer (30-60 seconds)
I treat the column label like a base-26 number, but each letter uses values 1 through 26 instead of 0 through 25. I start with result equal to 0 and read the label from left to right. For each character, I compute its value with ch - 'A' + 1, then update result as result * 26 + value. After all characters are processed, I return result. This takes O(n) time and O(1) auxiliary space.
A spreadsheet uses letters to identify columns instead of showing ordinary column numbers. A means column 1, Z means column 26, and after Z the labels continue with two letters such as AA. We need to turn one of these labels into its matching number. The useful idea is to build the answer from left to right. Each letter adds its own value, while the value already built must move one position before the next letter is added. This gives the correct number without extra storage.
Useful Questions to Ask the Interviewer
Can I assume the input is a valid non-empty uppercase spreadsheet column label?
Should the returned column number always fit in a Java int?
How to Explain It in an Interview
1. Understand the input and output
The input is a spreadsheet-style column label such as "A", "Z", or "AA". The output is its 1-indexed column number. Spreadsheet letters use values from 1 through 26. A is 1, B is 2, and Z is 26.
2. Use the base-26 idea
I read the label from left to right. The running result represents the value of the prefix already processed. For each new character, I first multiply the previous result by 26. This shifts the previous value one spreadsheet position to the left. Then I add the 1-based value of the current letter.
The update is: result = result * 26 + value
The current letter value is: value = ch - 'A' + 1
The central invariant is that after processing any prefix of the label, result equals the spreadsheet column number represented by that prefix.
3. Initialize the state
I start with result = 0. Traversal begins at index 0. No extra data structure is needed. The integer result stores the numeric value of the prefix processed so far.
4. Walk through the example
The diagram uses the input "AA" and the expected result 27.
At index 0, the character is A. Its numeric value is 1. The result before processing it is 0. I calculate 0 * 26 + 1, so result becomes 1. The processed prefix "A" therefore represents column 1.
At index 1, the character is again A. Its numeric value is 1. The result before processing it is 1. I calculate 1 * 26 + 1, so result becomes 27. All characters have now been processed, so the function returns 27.
The same result can be verified using place values: 1 * 26^1 + 1 * 26^0 = 26 + 1 = 27.
5. Explain why the result is correct
After each iteration, result equals the value of the prefix processed so far. Multiplying by 26 moves that prefix one base-26 position to the left. Adding the current letter value puts the new letter into the final position. Therefore, when all characters have been processed, result equals the numeric value of the complete spreadsheet label.
6. Explain the Java implementation, complexity, and edge cases
The Java method loops through the characters from left to right. It converts each uppercase letter to a value from 1 through 26 and applies result = result * 26 + value. After the last character, it returns result. The time complexity is O(n), where n is the label length. The auxiliary space is O(1) because only a few primitive variables are used. Relevant examples are A -> 1, Z -> 26, AA -> 27, and AZ -> 52. The shown solution assumes a valid non-empty uppercase spreadsheet label.
Key Insight / Why This Solution Works
The key insight is to interpret the label using spreadsheet base-26 place values, where A = 1 through Z = 26. We keep one running integer called result. For every character, multiplying result by 26 shifts the value already built one position to the left. We then add the current character's 1-based value. The invariant is: after processing a prefix of the string, result equals the spreadsheet column number represented by that prefix. After the final character, the processed prefix is the whole label, so result is the required answer.
Code
classSolution {
publicinttitleToNumber(String columnTitle) {
// No characters have been processed yet, so the running value starts at 0.intresult=0;
// Process the spreadsheet label from left to right, exactly as in the walkthrough.for (inti=0; i < columnTitle.length(); i++) {
// Read the current uppercase spreadsheet character.charch= columnTitle.charAt(i);
// Convert A..Z into the spreadsheet values 1..26 rather than 0..25.intvalue= ch - 'A' + 1;
// Shift the previous prefix one base-26 position, then add this character's value.
result = result * 26 + value;
}
// After the full label is processed, result is its 1-indexed column number.return result;
}
}
publicclassMain {
publicstaticvoidmain(String[] args) {
// Run the exact verified example from the diagram.StringcolumnTitle="AA";
Solutionsolution=newSolution();
// Convert "AA" to its spreadsheet column number and print the expected result, 27.intresult= solution.titleToNumber(columnTitle);
System.out.println(result);
}
}
Time & Space Complexity
Let n be the number of characters in the column label. The algorithm takes O(n) time because it visits each character once and performs a constant amount of work for that character. It uses O(1) auxiliary space because the extra memory does not grow with n. It only keeps a loop index, the current character, its numeric value, and the running result.
Where it is used
This pattern is useful when a sequence of symbols represents a number using place values. Spreadsheet column labels are one example. The same general idea is used when parsing decimal numbers, hexadecimal values, or other custom number formats: shift the value already built by the base, then add the value of the next symbol.
Why Interviewers Ask This
This question checks whether you can recognize positional number representation and turn it into a simple Java loop. The interviewer can see whether you understand why spreadsheet letters use A = 1 rather than A = 0, whether you update the running prefix value in the correct order, and whether your character arithmetic is correct. It also tests whether you can state a useful invariant, trace the example accurately, and explain the O(n) time and O(1) auxiliary space clearly.
Common interview mistakes
A common mistake is mapping A to 0 instead of 1. Spreadsheet labels use A = 1 through Z = 26. Another mistake is updating the running value in the wrong order. The correct operation is result = result * 26 + value, because the previous prefix must first move one base-26 position. Candidates may also forget that the position of each character matters and process the letters as independent values. Another mistake is claiming O(n) extra space even though the algorithm uses only a fixed number of primitive variables.
Interview tip
Use "AA" to explain the update before writing code. After the first A, result is 1. For the second A, calculate 1 * 26 + 1 = 27. That small trace makes both the base-26 idea and the loop update easy to explain.
Interviewer may ask next
How would you convert a column number back to a spreadsheet label?
The direction changes, but the same 1-based spreadsheet rule must be preserved. I would build the label from right to left. Before taking each remainder, I would subtract 1 from the number so that values 1 through 26 map correctly to A through Z. Then I would use number % 26 to choose the letter, append it, and divide the remaining value by 26. I would reverse the collected letters at the end. This is correct because each step removes the current least-significant spreadsheet digit. For a label of length k, the time is O(k) and the output-building space is O(k). The tradeoff is that this reverse conversion needs storage for the generated label.
What changes if the column label can be so long that the result does not fit in a Java int?
The left-to-right algorithm does not change. Only the numeric type changes. If long is large enough, I can store result as a long. For arbitrarily large labels, I can use BigInteger and replace result * 26 + value with result.multiply(BigInteger.valueOf(26)).add(BigInteger.valueOf(value)). The invariant remains the same, so correctness is preserved. The algorithm still processes n input characters, but BigInteger operations become more expensive as the stored number grows. The extra memory is no longer O(1) because the BigInteger itself grows with the size of the result. The tradeoff is higher computation and memory cost in exchange for avoiding overflow.
More questions load as you scroll
Java Developer Resume Examples
Explore the resume examples below to find the one that best matches your target Java Developer role.
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.