34 Amazon Java Developer Interview Questions & Answers

amazon icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

1. How would you count words in a string without libraries?CodingEasyAmazon

Question Details

Count the words in a string without splitting the sentence or using regular expressions.

Short Interview Answer (30-60 seconds)

I would scan the string from left to right and keep a boolean called inWord. I start with count equal to 0 and inWord false. Whitespace sets inWord to false. When I see a non-whitespace character while inWord is false, I know a new word has started, so I increment count and set inWord to true. This counts each word exactly once. The time complexity is O(n), and the auxiliary space complexity is O(1).

Detailed Explanation

See the Code while reading this explanation.

The goal is to count how many separate words appear in the string without splitting the sentence or using a regular expression. A word is a continuous run of non-whitespace characters. I read the characters from left to right and remember whether I am currently inside a word. I count a word only when I move from the start of the string or whitespace into a non-whitespace character. This avoids counting several letters from the same word more than once.

Useful Questions to Ask the Interviewer
  1. Should spaces, tabs, and newlines all be treated as whitespace between words?
  2. Can the input be null, and if so, should it return 0?
How would you count words in a string without libraries? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is one string. The output is the number of words in that string. A word is a maximal continuous run of non-whitespace characters. We do not call split() and we do not use regular expressions. For the diagram example, s = "go to java", the answer is 3 because the words are "go", "to", and "java".

2. Choose the state we need

We only need two main variables. count stores how many word starts we have found. inWord tells us whether we are currently inside a word. The central rule is simple: increment count only when the current character is not whitespace and inWord is false.

3. Initialize the state

Start with count = 0 because no words have been seen. Start with inWord = false because we are outside a word before reading the string. Traversal begins at index 0 and moves from left to right.

4. Walk through the example

The exact string is "go to java", which has length 11. Its characters at indices 0 through 10 are g, o, space, space, t, o, space, j, a, v, a.

At index 0, the character is g. The state before processing it is inWord = false. It is not whitespace, so a new word starts. We increment count to 1 and set inWord = true.

At index 1, o is not whitespace and inWord is already true. It continues the same word, so count stays 1.

At index 2, the character is whitespace. We leave the current word by setting inWord = false. The count stays 1.

At index 3, there is another whitespace character. We are already outside a word, so inWord remains false and count remains 1.

At index 4, the character is t. Because inWord is false, this starts the second word. We increment count to 2 and set inWord = true.

At index 5, o continues the same word, so the count remains 2.

At index 6, whitespace ends the current word, so inWord becomes false.

At index 7, j is non-whitespace while inWord is false. It starts the third word. We increment count to 3 and set inWord = true.

At indices 8, 9, and 10, the characters a, v, and a continue the same word. The count stays 3. After the loop finishes, we return count = 3.

5. Explain why the result is correct

A word is counted exactly when a new non-whitespace run begins. Once we enter a word, inWord becomes true. This prevents later characters in that same word from increasing the count. When whitespace appears, inWord becomes false. The next non-whitespace character can then start another word. Therefore every word is counted exactly once.

6. Explain the Java implementation

The method first returns 0 for a null or empty string. It then initializes count = 0 and inWord = false. The loop reads one character at a time with charAt(i). Character.isWhitespace(c) detects whitespace such as spaces, tabs, and newlines. Whitespace sets inWord to false. A non-whitespace character increments count only when inWord was false. After all characters are processed, the method returns count.

7. Explain complexity and edge cases

If the string contains n characters, the algorithm examines each character once, so the time complexity is O(n). It uses only a fixed number of variables, so the auxiliary space complexity is O(1). Relevant edge cases are null or empty input, an all-whitespace string, multiple spaces between words, leading or trailing whitespace, and a string containing one word with no spaces.

Key Insight / Why This Solution Works

The key insight is to count the start of each word instead of counting every non-whitespace character. The algorithm keeps a boolean state called inWord. The invariant is: after each processed character, count equals the number of word starts seen so far, and inWord tells whether we are currently inside a word. Whitespace sets inWord to false. A non-whitespace character increases count only when inWord is false. This makes every maximal non-whitespace run contribute exactly one to the result.

Code
public class Main {

    public static int countWords(String s) {
        // A null or empty string contains no words.
        if (s == null || s.isEmpty()) {
            return 0;
        }

        // count stores how many word starts have been found so far.
        int count = 0;

        // inWord records whether the current processed position is inside a word.
        boolean inWord = false;

        // Process the string from left to right, one character at a time.
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            // Whitespace ends the current word. This allows a later
            // non-whitespace character to start and count a new word.
            if (Character.isWhitespace(c)) {
                inWord = false;
            } else if (!inWord) {
                // We are entering a new run of non-whitespace characters,
                // so count this word exactly once.
                count++;
                inWord = true;
            }
            // Otherwise, c is another character in the current word.
            // The state and count do not need to change.
        }

        // After every character is processed, count is the number of words.
        return count;
    }

    public static void main(String[] args) {
        // Exact example from the approved diagram. There are two spaces after "go".
        String s = "go  to java";

        // The words are "go", "to", and "java", so the returned result is 3.
        System.out.println(countWords(s));
    }
}
Time & Space Complexity

Let n be the number of characters in the string. The loop examines each character exactly once, so the time complexity is O(n). The extra memory does not grow with n. The algorithm only keeps variables such as count, inWord, the loop index, and the current character. Therefore the auxiliary space complexity is O(1).

Where it is used

This state-tracking pattern is useful when software processes text one character at a time and needs to detect boundaries between groups. For example, it can count whitespace-separated tokens without creating an array of substrings. The same idea is also useful for streaming text because the algorithm only needs the current character and a small amount of state.

Why Interviewers Ask This

This question tests whether you can solve a string-scanning problem with a small amount of state instead of relying on split() or regular expressions. The interviewer can evaluate whether you identify word boundaries correctly, maintain a useful invariant, handle repeated and leading or trailing whitespace, write clean Java, and explain why each word is counted exactly once. It also checks whether you can state the O(n) time and O(1) auxiliary space complexities correctly.

Common interview mistakes

A common mistake is incrementing count for every non-whitespace character, which counts letters instead of words. Another mistake is forgetting to set inWord to false when whitespace appears. Candidates may also check only the literal space character and miss tabs or newlines, while the approved solution uses Character.isWhitespace(c). Consecutive spaces must not create extra words. Another mistake is using split() or a regular expression even though the question asks for direct character scanning.

Interview tip

Describe the algorithm as detecting the transition from outside a word to inside a word. Only that inWord = false to inWord = true transition increments the count, which makes the code and correctness argument easy to explain.

Interviewer may ask next
How does this solution handle tabs and newlines instead of only normal spaces?

The algorithm already handles them because it uses Character.isWhitespace(c). A tab, newline, or other Java whitespace character sets inWord to false. The next non-whitespace character then starts a new word. The algorithm is unchanged. The time complexity remains O(n), and the auxiliary space remains O(1).

How would the solution change if the characters arrived as a stream instead of one complete string?

The same state-based algorithm can be used. Keep count and inWord between incoming characters. Whitespace sets inWord to false. A non-whitespace character increments count only when inWord was false, then sets it to true. No earlier characters need to be stored. Processing m streamed characters takes O(m) time and O(1) extra space. The tradeoff is that the final count is complete only when the stream ends.

2. How would you determine whether a word can be built from characters in a string?CodingEasyAmazon

Question Details

Given two strings, determine whether all letters needed for the target word exist in the source string.

Short Interview Answer (30-60 seconds)

I would use a HashMap to count how many times each character appears in the source string. Then I would scan the target from left to right. For each target character, I check its remaining count. If the count is zero, I return false immediately. Otherwise, I decrease the count by one. If every target character is processed, I return true. This takes O(s + t) expected time and O(k) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The problem gives me a source string and a target word. I need to decide whether the source has enough copies of every letter needed by the target. A letter may appear more than once, so I must count how many copies are available. For example, target "apple" needs two p characters. The source "appple" has three p characters, so it has enough. I can store these counts and reduce them as I use letters from the target.

Useful Questions to Ask the Interviewer
  1. Should character matching be case-sensitive?
  2. Can I assume both strings are non-null?
  3. Should repeated characters in the target require the same number of copies in the source?
How would you determine whether a word can be built from characters in a string? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is two strings: source and target. I return true when source contains every character needed by target with enough frequency. Otherwise, I return false. I am checking character counts, not positions or substrings.

For the diagram example, source is "appple" and target is "apple". The expected result is true.

The source characters by index are 0:a, 1:p, 2:p, 3:p, 4:l, 5:e. The target characters are 0:a, 1:p, 2:p, 3:l, 4:e.

2. Choose the algorithm and data structure

I use a HashMap<Character, Integer>. Each key is a character. Each value is the number of unused copies of that character that are still available from source.

I first count all source characters. Then I scan target. For each target character, I check its count in the map. If the count is zero or the character is missing, the target cannot be built. Otherwise, I consume one copy by decreasing the count.

The main invariant is simple: before each target step, the map stores exactly how many unused copies of every source character are still available.

3. Initialize the state

The map starts empty. I scan source from index 0 to the end.

For source "appple", the map changes like this: S0 reads a: {} becomes {a:1}. S1 reads p: {a:1} becomes {a:1, p:1}. S2 reads p: {a:1, p:1} becomes {a:1, p:2}. S3 reads p: {a:1, p:2} becomes {a:1, p:3}. S4 reads l: {a:1, p:3} becomes {a:1, p:3, l:1}. S5 reads e: {a:1, p:3, l:1} becomes {a:1, p:3, l:1, e:1}.

Now the map is the complete inventory of available characters.

4. Walk through the example

I now scan target "apple" from left to right.

At T0, the current character is a. The map is {a:1, p:3, l:1, e:1}. The count for a is 1, so I use one a. The map becomes {a:0, p:3, l:1, e:1}. Processing continues.

At T1, the current character is p. The count for p is 3. I use one p. The map becomes {a:0, p:2, l:1, e:1}.

At T2, the current character is p again. The count for p is still 2 before the update, so the second p is valid. I decrease it to 1. The map becomes {a:0, p:1, l:1, e:1}.

At T3, the current character is l. Its count is 1. I decrease it to 0. The map becomes {a:0, p:1, l:0, e:1}.

At T4, the current character is e. Its count is 1. I decrease it to 0. The map becomes {a:0, p:1, l:0, e:0}.

All five target characters were matched, so the method returns true.

5. Explain why the result is correct

Before every target step, the map tells me how many unused copies of each source character remain. Every successful match consumes exactly one required copy. If a needed character has count 0, there is no unused copy left, so building the target is impossible. If I finish the target without failing, every required character has been matched. Therefore, returning true is correct.

6. Explain the Java implementation

The method first handles two simple cases. An empty target returns true. If source is shorter than target, it returns false because source cannot provide enough total characters.

Next, the first loop builds the frequency map from source. The second loop reads each target character. It gets the remaining count with getOrDefault. If that value is 0, it returns false immediately. Otherwise, it stores remaining minus one. If the loop completes, it returns true.

7. Explain complexity and edge cases

Let s be source.length() and t be target.length(). Building the map processes source once. Checking the target processes each target character at most once. Java HashMap lookup and insertion are O(1) on average, so the expected time is O(s + t).

The map stores one entry for each distinct source character. If k is the number of distinct characters in source, the auxiliary space is O(k).

Important edge cases are an empty target, a source shorter than the target, repeated characters, a missing character, and extra source characters. Extra characters do not hurt because they can simply remain unused.

Key Insight / Why This Solution Works

The key idea is to treat the source string as an inventory of available characters. A HashMap stores character -> remaining count. I first count every source character. Then I consume one count for every character required by target. The invariant is that before each target step, the map contains exactly the unused copies still available from source. If a required count is 0, the answer is false. If every target character is consumed, the answer is true. This fits repeated characters correctly because it checks frequency, not only presence.

Code
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static boolean canBuildWord(String source, String target) {
        // An empty target needs no characters, so it can always be built.
        if (target.length() == 0) {
            return true;
        }

        // A shorter source cannot provide enough total characters for the target.
        if (source.length() < target.length()) {
            return false;
        }

        // Each map key is a source character. Its value is the unused count available.
        Map<Character, Integer> freq = new HashMap<>();

        // Scan source from left to right and build the complete frequency map.
        for (int i = 0; i < source.length(); i++) {
            char c = source.charAt(i);
            freq.put(c, freq.getOrDefault(c, 0) + 1);
        }

        // Scan target from left to right and consume one available copy per character.
        for (int i = 0; i < target.length(); i++) {
            char c = target.charAt(i);

            // Missing characters and characters with no unused copies both give 0.
            int remaining = freq.getOrDefault(c, 0);

            // Stop immediately when the source cannot supply this target character.
            if (remaining == 0) {
                return false;
            }

            // Consume exactly one copy so repeated target characters are handled correctly.
            freq.put(c, remaining - 1);
        }

        // Reaching this point means every target character was supplied successfully.
        return true;
    }

    public static void main(String[] args) {
        // Run the exact verified example from the diagram.
        String source = "appple";
        String target = "apple";

        // The expected output for this example is true.
        System.out.println(canBuildWord(source, target));
    }
}
Time & Space Complexity

Let s be the length of source and t be the length of target. We process source once to build the frequency map. We then process target at most once and may stop early if a needed character is unavailable. HashMap lookup and insertion are O(1) on average, so the expected time is O(s + t). If k is the number of distinct characters stored from source, the extra memory is O(k). This is auxiliary space, meaning memory used in addition to the input.

Where it is used

This frequency-count pattern is useful when software must check whether one collection has enough copies of items required by another collection. Examples include checking available inventory against an order, validating whether a pool of characters can form a word, comparing required resource counts with available resource counts, and detecting shortages while processing requests.

Why Interviewers Ask This

This question tests whether you recognize that character presence is not enough and that frequency must be tracked. The interviewer can evaluate your choice of a HashMap, your handling of repeated characters, your ability to maintain a clear invariant, and your use of early return when a required count is unavailable. It also tests whether you can write correct Java loops and explain expected HashMap complexity without incorrectly presenting average constant-time operations as a guaranteed worst-case bound.

Common interview mistakes

A common mistake is checking only whether each character exists in source. That fails when frequency matters. For example, "aple" contains p, but it does not contain the two p copies required by "apple". Another mistake is forgetting to decrement a character's count after using it. Candidates may also build the frequency map from target instead of source even though the diagram's algorithm stores the remaining source inventory. Another mistake is continuing after a required count is 0 instead of returning false immediately. Finally, do not claim guaranteed O(s + t) time because Java HashMap lookup and insertion are O(1) on average.

Interview tip

State the map meaning before you code: character -> unused copies remaining in source. Then explain that each target character consumes one copy. This makes repeated-character handling and the correctness invariant easy to follow.

Interviewer may ask next
How would the solution handle a very large source that arrives as a stream?

I can build the same frequency map while reading the source stream. Each incoming source character increments its count. After the stream finishes, I scan target and consume counts exactly as in the original solution. The invariant is unchanged because the completed map still represents the available source inventory. The expected time is O(s + t), and the auxiliary space is O(k), where k is the number of distinct source characters. The tradeoff is that the final target check normally waits until the source stream is complete.

Can auxiliary space be reduced if the allowed character set is known and small?

Yes. If the problem guarantees a fixed small alphabet, such as only lowercase English letters, I can replace the HashMap with an int array of size 26. I increment counts for source and decrement them for target in the same processing order. Each array entry still means the number of unused copies available, so the same correctness argument applies. Time is O(s + t), and auxiliary space becomes O(1) because the array size is fixed. The tradeoff is that this version depends on the restricted alphabet guarantee.

3. How would you find the longest substring with equal numbers of alphabets and digits?CodingMediumAmazon

Question Details

Given a string with letters and numbers, find the longest substring that has the same count of letters and digits.

Short Interview Answer (30-60 seconds)

I would use a prefix balance and a hash map. I treat each letter as +1 and each digit as -1. The map stores each balance and the earliest index where it appeared. If the same balance appears again, the substring between those positions has equal letters and digits. I scan from left to right and keep the longest such substring. This gives O(n) expected time because HashMap operations are O(1) on average, with O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is a string containing letters and digits. We need to return the longest continuous part of the string where the number of letters equals the number of digits. For the example "A1BC23D4E", the answer is "A1BC23D4". It contains four letters and four digits. The main idea is to keep a running balance. A letter adds one and a digit subtracts one. When the same balance appears at two positions, the substring between those positions is balanced. A hash map remembers the earliest position for each balance.

Useful Questions to Ask the Interviewer
  1. Can I assume the input contains only alphabetic characters and digits?
  2. If several longest balanced substrings have the same length, should I keep the earliest one?
How would you find the longest substring with equal numbers of alphabets and digits? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a string. The output is the longest contiguous substring with the same number of letters and digits. Contiguous means the characters must stay next to each other in the original string. For the example "A1BC23D4E", the result is "A1BC23D4", which covers indices 0 through 7.

2. Choose the algorithm and data structure

I keep a running value called balance. A letter changes balance by +1. A digit changes it by -1. I also use a HashMap named firstSeen. It stores balance -> earliest index. The key idea is that if the same balance appears at two positions, the substring between them has a net balance of zero. That means it contains equal numbers of letters and digits.

3. Initialize the state

Before reading any character, balance is 0. I put 0 -> -1 into firstSeen. This represents a zero balance before the string starts. I also set bestStart = 0 and maxLen = 0. Storing index -1 is important because it lets a balanced substring starting at index 0 be measured correctly.

4. Walk through the example

For s = "A1BC23D4E": At i = 0, 'A' is a letter, so balance becomes 1. Balance 1 is new, so store 1 -> 0. At i = 1, '1' is a digit, so balance returns to 0. Balance 0 was first seen at -1. The length is 1 - (-1) = 2, so the best substring becomes "A1". At i = 2, 'B' makes balance 1. It was first seen at 0. The candidate length is 2, so the current best stays "A1". At i = 3, 'C' makes balance 2. It is new, so store 2 -> 3. At i = 4, '2' makes balance 1. Balance 1 was first seen at 0. The candidate length is 4, so the best becomes "1BC2". At i = 5, '3' makes balance 0. Balance 0 was first seen at -1. The candidate length is 6, so the best becomes "A1BC23". At i = 6, 'D' makes balance 1. The candidate length is 6. This ties the current best, so no update is made. At i = 7, '4' makes balance 0. The candidate length is 8, so the best becomes "A1BC23D4". At i = 8, 'E' makes balance 1. The candidate length is 8. This is another tie, so the earlier answer remains. The final result is "A1BC23D4".

5. Explain why the result is correct

Balance means letters minus digits. If the balance has the same value at two positions, the change in balance between those positions is zero. Therefore, that substring added the same number of +1 values as -1 values, so it contains the same number of letters and digits. Keeping the earliest index for every balance gives the longest possible candidate ending at the current position. Taking the longest candidate over the whole scan gives the global longest balanced substring.

6. Explain the Java implementation

The Java code creates firstSeen and starts it with 0 -> -1. It scans the string from left to right. Character.isLetter tells us whether to add 1 or subtract 1. If the current balance already exists in the map, the code gets the earliest index prev and calculates len = i - prev. If len is larger than maxLen, it updates maxLen and bestStart. If the balance is new, the code stores the current index. After the loop, it returns s.substring(bestStart, bestStart + maxLen).

7. Explain complexity and edge cases

The expected time is O(n). Each character is processed once, and Java HashMap lookup and insertion are O(1) on average, with hashing and collision caveats. The auxiliary space is O(n) because the map can store many different balance values. For an empty string, one character, all letters, or all digits, the returned substring is empty. If several longest answers tie, this code keeps the earliest one because it updates only when len > maxLen.

Key Insight / Why This Solution Works

The key insight is to convert the problem into a prefix-balance problem. Define balance as letters minus digits. Each letter contributes +1 and each digit contributes -1. If the balance at index i is the same as it was at an earlier index prev, then the net balance from prev + 1 through i is zero. Therefore, that substring contains equal numbers of letters and digits. The HashMap stores balance -> earliest index. Keeping only the earliest index is important because it creates the longest possible candidate when that balance appears again. The invariant is that every map entry stores the earliest index where its prefix balance occurred.

Code
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static String longestBalancedSubstring(String s) {
        // Store each prefix balance and the earliest index where it appeared.
        Map<Integer, Integer> firstSeen = new HashMap<>();

        // A zero balance exists before the first character.
        // This lets a balanced substring starting at index 0 be measured correctly.
        firstSeen.put(0, -1);

        // balance = number of letters seen - number of digits seen.
        int balance = 0;

        // Track the starting index and length of the longest answer found so far.
        int bestStart = 0;
        int maxLen = 0;

        // Process the string from left to right.
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            // A letter contributes +1. Because the input contains letters and digits,
            // every non-letter here is treated as a digit and contributes -1.
            balance += Character.isLetter(ch) ? 1 : -1;

            if (firstSeen.containsKey(balance)) {
                // The same prefix balance appeared earlier.
                // Therefore, the substring from prev + 1 through i has net balance 0.
                int prev = firstSeen.get(balance);
                int len = i - prev;

                // Update only for a strictly longer substring.
                // A tie keeps the earlier longest substring already stored.
                if (len > maxLen) {
                    maxLen = len;
                    bestStart = prev + 1;
                }
            } else {
                // Keep only the earliest index for this balance.
                // The earliest occurrence gives the longest future candidate.
                firstSeen.put(balance, i);
            }
        }

        // If no non-empty balanced substring exists, maxLen stays 0,
        // so substring(0, 0) correctly returns the empty string.
        return s.substring(bestStart, bestStart + maxLen);
    }

    public static void main(String[] args) {
        // Run the exact example shown in the approved diagram.
        String s = "A1BC23D4E";
        String result = longestBalancedSubstring(s);

        // Expected output: A1BC23D4
        System.out.println(result);
    }
}
Time & Space Complexity

Let n be the number of characters in the string. The expected time is O(n). We process each character once. For each character, we do a HashMap lookup and sometimes an insertion. Java HashMap lookup and insertion are O(1) on average, although hashing and collisions mean this is an expected-time statement rather than a guaranteed worst-case bound. The auxiliary space is O(n) because firstSeen may store a different balance for many positions.

Where it is used

This prefix-balance pattern is useful when a contiguous part of data must contain equal amounts of two kinds of items. Examples include equal counts of two event types in a stream or equal counts of two symbols in a sequence. The pattern assigns one group a positive contribution and the other group a negative contribution, then looks for repeated prefix balances.

Why Interviewers Ask This

This problem checks whether you can recognize a prefix-balance pattern instead of trying every substring. It tests whether you can choose a HashMap with the correct key and value meaning, keep the earliest occurrence instead of overwriting it, maintain a clear invariant, and translate that reasoning into correct Java. It also checks whether you describe expected HashMap performance accurately and handle ties and boundary cases carefully.

Common interview mistakes

A common mistake is treating this as a subsequence problem. The answer must be a contiguous substring. Another mistake is storing the latest index for a balance instead of the earliest one, which can lose a longer answer. Candidates may also forget the initial map entry 0 -> -1, which is needed for balanced substrings starting at index 0. Another error is updating on len >= maxLen when the intended behavior is to keep the earliest longest substring. Finally, do not claim guaranteed O(n) time. HashMap operations are O(1) on average, so the overall bound is O(n) expected time.

Interview tip

State the invariant before writing code: firstSeen stores the earliest index for every prefix balance, and seeing the same balance again means the characters between those positions contain equal numbers of letters and digits. Then implement that invariant directly.

Interviewer may ask next
What changes if I need to return all longest balanced substrings instead of only the earliest one?

The same prefix-balance method can still be used. Keep the earliest index for each balance. When a candidate length is larger than the current maximum, clear the result list and add that substring. When the candidate length equals the current maximum, add it as another longest result. The repeated-balance invariant is unchanged. The expected scan time remains O(n), excluding the cost of creating or returning the substrings. The map uses O(n) auxiliary space, while the result list adds output space. The tradeoff is that the output can be much larger.

What changes if I only need the start and end indices instead of the substring itself?

The algorithm does not change. Keep bestStart and maxLen exactly as before. At the end, return bestStart and bestStart + maxLen - 1 instead of calling substring. For the example, the result is indices [0, 7]. The same repeated-balance invariant proves correctness. The expected time remains O(n), and the auxiliary space remains O(n). The main benefit is that no output substring has to be created.

4. How would you find the number with a duplicate entry?CodingEasyAmazon

Question Details

Given a list of numbers where all numbers except one are unique, find the repeated value.

Short Interview Answer (30-60 seconds)

I would use a HashSet to remember numbers I have already seen. I scan the list from left to right. For each value, I first check whether it is already in the set. If it is, that value is the duplicate, so I return it immediately. Otherwise, I add it to the set and continue. I process each item at most once. This takes O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is a list of numbers where one value appears more than once and the other values are unique. The goal is to return the repeated value, not its index. I can remember every value already processed. When I reach a value that I have seen before, I know that value is the duplicate. A HashSet is a good fit because it can usually check and store a value in constant time on average. The algorithm also stops as soon as the repeated value is found.

Useful Questions to Ask the Interviewer
  1. Is exactly one repeated value guaranteed?
  2. Should I return the repeated value itself rather than its index?
  3. Can the input contain negative numbers or zero?
How would you find the number with a duplicate entry? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a list of numbers. All numbers except one are unique. I need to return the repeated value itself. For the example [4, 1, 7, 1, 9], the correct result is 1 because 1 appears more than once.

2. Choose the algorithm and data structure

I use a HashSet<Integer> named seen. The set stores values from positions I have already processed. The main invariant is simple: before checking the current number, seen contains only values from earlier positions. If the current value is already in seen, that value must have appeared before, so it is the repeated value.

3. Initialize the state

At the start, the array is [4, 1, 7, 1, 9]. The indices are [0, 1, 2, 3, 4]. The set is empty: {}. Traversal begins at index 0 and moves from left to right.

4. Walk through the example

At index 0, the current value is 4. The set is {}. 4 is not present, so I add it. The set becomes {4} and processing continues.

At index 1, the current value is 1. The set is {4}. 1 is not present, so I add it. The set becomes {4, 1}.

At index 2, the current value is 7. The set is {4, 1}. 7 is not present, so I add it. The set becomes {4, 1, 7}.

At index 3, the current value is 1. The set is {4, 1, 7}. This time, 1 is already present. That proves 1 appeared earlier, so I return 1 and stop immediately. Index 4, whose value is 9, is not processed.

5. Explain why the result is correct

The set contains only values from earlier positions. Therefore, when the current value is already in the set, that same value has appeared before. Since the problem asks for the repeated value, returning it is correct. We process each element at most once and stop when the valid result is found.

6. Explain the Java implementation

The Java method creates an empty HashSet<Integer>. It loops through the array from left to right. For each number, it checks seen.contains(num) before inserting the number. If the lookup succeeds, the method returns that number immediately. Otherwise, it adds the number to the set. A defensive -1 return remains after the loop, but it is not the expected path because the stated problem guarantees a repeated value.

7. Explain complexity and edge cases

HashSet lookup and insertion are O(1) on average. Because the input is processed at most once, the overall expected time is O(n). The set can store up to O(n) values, so auxiliary space is O(n). An early duplicate returns sooner. A value repeated more than twice is detected on its second occurrence. Negative numbers and zero work normally with HashSet.

Key Insight / Why This Solution Works

The key idea is to remember values that appeared earlier. Use a HashSet named seen. Before adding the current value, check whether it is already in the set. If it is present, the value has appeared before, so it is the duplicate and can be returned immediately. If it is not present, add it and continue. The invariant is that seen contains only values from earlier positions. This gives fast average lookup without needing a slower repeated comparison against earlier elements.

Code
import java.util.HashSet;
import java.util.Set;

public class Main {

    public static int findRepeatedValue(int[] nums) {
        // Store values that have already been processed.
        Set<Integer> seen = new HashSet<>();

        // Traverse from left to right and stop as soon as the repeated value is found.
        for (int num : nums) {
            // Check before insertion. If present, this value appeared earlier.
            if (seen.contains(num)) {
                // Return immediately so later values are not processed.
                return num;
            }

            // The value is new, so remember it for later checks.
            seen.add(num);
        }

        // Defensive fallback; the stated problem guarantees a solution.
        return -1;
    }

    public static void main(String[] args) {
        // Use the exact example from the diagram.
        int[] nums = { 4, 1, 7, 1, 9 };

        // Run the algorithm and print the repeated value, which is 1.
        int repeatedValue = findRepeatedValue(nums);
        System.out.println(repeatedValue);
    }
}
Time & Space Complexity

Let n be the number of input values. We process the input at most once. A Java HashSet lookup and insertion are O(1) on average, so the overall expected time is O(n). This is expected time rather than a guaranteed worst-case hashing bound. The HashSet can hold up to n values in the worst case before the duplicate is found, so the auxiliary space is O(n).

Where it is used

This pattern is useful when software needs to detect whether an item has already appeared. Examples include finding repeated IDs, detecting duplicate records while reading data, checking repeated events in a stream, and validating that values are unique. A HashSet is useful when fast membership checks matter and the program does not need to store an index or a count for each value.

Why Interviewers Ask This

This question tests whether the candidate recognizes that fast membership checking can replace repeated comparisons. It also checks whether the candidate can choose an appropriate Java data structure, maintain a simple invariant, handle duplicates correctly, and reason about early return. The interviewer can also see whether the candidate distinguishes values from indices and explains HashSet complexity accurately as O(1) average lookup and insertion, leading to O(n) expected time with O(n) extra space.

Common interview mistakes

A common mistake is returning an index instead of the repeated value, because this question asks for the value itself. Another mistake is changing the lookup and insertion logic in a way that no longer matches the explained invariant. Candidates may also continue processing after the duplicate has been found instead of returning immediately. Another common error is claiming guaranteed O(n) time instead of O(n) expected time for a HashSet-based solution. Finally, do not describe the last value 9 as processed in the example because the algorithm stops at the second 1.

Interview tip

State the invariant early: before checking the current number, the HashSet contains only values from earlier positions. Then explain that a successful membership check proves the current value is repeated and allows an immediate return.

Interviewer may ask next
What would change if the input were not guaranteed to contain a duplicate?

The HashSet algorithm can stay the same. I would scan from left to right and return immediately if a value is already in the set. If the loop finishes without finding one, I would return a result that clearly represents no duplicate, such as OptionalInt.empty(), if that return contract is allowed. The expected time remains O(n), and the auxiliary space remains O(n). The main tradeoff is that the method now needs an explicit no-result representation.

How would this work if the numbers arrived as a stream instead of being stored in an array?

I can use the same HashSet idea. For each incoming number, I first check whether it is already in seen. If it is, I have found the repeated value. Otherwise, I add it and wait for the next number. The invariant stays the same because the set contains only values received earlier. Processing is O(1) expected time per item and O(n) expected time for n received items, with O(n) space. The tradeoff is that memory grows as new unique values arrive.

5. How would you find a duplicate in a list of numbers with constraints?CodingEasyAmazon

Question Details

Find the duplicate element in a number list using an efficient approach and explain the tradeoff.

Short Interview Answer (30-60 seconds)

I would scan the list from left to right and keep a HashSet of values I have already seen. For each value, I first check the set. If the value is already there, I return it immediately because it is a duplicate. Otherwise, I add it and continue. I process each item at most once and stop when the answer is found. This takes O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is a list of integers, and the goal is to return a value that appears more than once. I read the numbers from left to right and remember each earlier value. Before saving the current value, I check whether I have already seen it. If I have, that value is duplicated, so I return it immediately. This avoids comparing every number with every other number. The tradeoff is that we use extra memory to get faster expected running time.

Useful Questions to Ask the Interviewer
  1. Is a duplicate guaranteed to exist, or should I return a fallback value when there is none?
  2. If several values are duplicated, should I return the first repeated value detected during a left-to-right scan?
  3. Am I allowed to use O(n) extra memory for a HashSet?
How would you find a duplicate in a list of numbers with constraints? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is a list of integer values. The output is a duplicated value, not an index. In the diagram, the example is nums = [4, 1, 3, 2, 3, 5]. The returned value is 3. The implementation also has a defensive fallback of -1 if the scan finishes without finding a duplicate.

2. Choose the algorithm and data structure

I use a HashSet<Integer> named seen. A set stores unique values. Here, seen represents the distinct values from positions that have already been processed. Before each step, this is the main invariant: seen contains exactly the distinct values from the already processed part of the list. A HashSet is useful because lookup and insertion are O(1) on average.

3. Initialize the state

At the beginning, seen is empty: { }. Traversal starts at index 0. No values have been processed yet, so the empty set correctly represents the initial state.

4. Walk through the example

The example is [4, 1, 3, 2, 3, 5].

At index 0, the current value is 4. seen is { }. The check 4 in seen is false. I add 4. seen becomes {4}, and processing continues.

At index 1, the current value is 1. seen is {4}. The check 1 in seen is false. I add 1. seen becomes {4, 1}, and processing continues.

At index 2, the current value is 3. seen is {4, 1}. The check 3 in seen is false. I add 3. seen becomes {4, 1, 3}, and processing continues.

At index 3, the current value is 2. seen is {4, 1, 3}. The check 2 in seen is false. I add 2. seen becomes {4, 1, 3, 2}, and processing continues.

At index 4, the current value is 3. seen is {4, 1, 3, 2}. The check 3 in seen is true. This proves that 3 appeared earlier, so the algorithm stops and returns 3. The value 3 appears at indices 2 and 4. Five of the six elements were processed. Index 5, whose value is 5, is not processed because the method has already returned.

5. Explain why the result is correct

Before each iteration, seen contains all distinct values from earlier processed positions. If the current value is already in seen, it must have occurred at an earlier position, so it is a duplicate. If the value is not present, adding it keeps the invariant true for the next iteration. Therefore, returning 3 when the second 3 is reached is correct.

6. Explain the Java implementation

The Java method creates a HashSet<Integer>. It uses a for-each loop to read values from left to right. For each value, it calls seen.contains(value) before insertion. If that lookup succeeds, it returns the value immediately. Otherwise, seen.add(value) stores it for later checks. If the loop finishes without finding a duplicate, the implementation returns -1 as a defensive fallback.

7. Explain complexity and edge cases

HashSet lookup and insertion are O(1) on average, so processing at most n values gives O(n) expected time. The set can hold up to O(n) distinct values, so auxiliary space is O(n). Relevant cases include a duplicate near the beginning, negative values, zero, and all values being the same. If the input is empty or contains no duplicate, this implementation reaches the defensive fallback and returns -1.

Key Insight / Why This Solution Works

The key idea is to remember values that appeared earlier. Use a HashSet<Integer> called seen. Before processing each value, seen contains exactly the distinct values from the already processed positions. Check the current value before inserting it. If seen already contains it, the same value must have appeared earlier, so it is a duplicate and can be returned immediately. Otherwise, add it and continue. This avoids comparing each value with every other value. The tradeoff is extra O(n) memory in exchange for O(n) expected time.

Code
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {

    public static int findDuplicate(List<Integer> nums) {
        // Store each distinct value that has already been processed.
        Set<Integer> seen = new HashSet<>();

        // Read values from left to right and stop as soon as a repeat is found.
        for (int value : nums) {
            // Check before insertion so a successful lookup means this value appeared earlier.
            if (seen.contains(value)) {
                // Return the repeated value immediately. Later values are not processed.
                return value;
            }

            // This value is new, so remember it for future duplicate checks.
            seen.add(value);
        }

        // Defensive fallback when the input contains no duplicate.
        return -1;
    }

    public static void main(String[] args) {
        // Use the exact example shown in the approved diagram.
        List<Integer> nums = List.of(4, 1, 3, 2, 3, 5);

        // Run the algorithm. The second 3 is detected at index 4.
        int duplicate = findDuplicate(nums);

        // The expected output for the diagram's example is 3.
        System.out.println(duplicate); // 3
    }
}
Time & Space Complexity

Let n be the number of values in the list. We process the input at most once. Java HashSet lookup and insertion are O(1) on average, with hashing and collision caveats. Therefore, the overall expected time is O(n). The set may store up to n distinct values, so the auxiliary space is O(n). This is the standard optimal expected-time approach for this general solution. The extra memory used by the set is the main tradeoff.

Where it is used

This pattern is useful when software needs to know whether a value has appeared before while reading data. Examples include detecting repeated IDs, duplicate records, repeated events, or repeated values during validation. A HashSet is a good fit when fast membership checks are more important than minimizing extra memory.

Why Interviewers Ask This

This question checks whether you recognize that fast membership testing is the main need. The interviewer can see whether you choose an appropriate Java data structure, maintain a clear invariant, handle repeated values correctly, and stop as soon as the result is known. It also tests whether you distinguish values from indices and explain the memory tradeoff accurately. A strong answer describes HashSet operations as average O(1) and the full algorithm as O(n) expected time with O(n) extra space.

Common interview mistakes

A common mistake is inserting the current value before checking whether it was already present. The correct order is lookup first, then insertion only when the value is new. Another mistake is returning an index even though this problem asks for the duplicated value. Candidates may also continue processing values after the duplicate has already been found instead of returning immediately. Another error is claiming guaranteed O(n) time rather than O(n) expected time for the hash-based approach. Finally, do not claim O(1) auxiliary space because the HashSet can grow with the input.

Interview tip

State the invariant before you code: the HashSet contains only distinct values from earlier processed positions. Then say that you check the current value before inserting it. This makes the duplicate logic and the early return easy to justify.

Interviewer may ask next
What would you change if no duplicate were guaranteed to exist?

The HashSet scan can stay the same, but the no-duplicate result should become an explicit part of the method contract. The current implementation returns -1 after the loop as a defensive fallback. If -1 could also be a valid input value, I would prefer a return type such as OptionalInt so absence is unambiguous. The invariant and lookup-before-insertion order stay unchanged. Expected time remains O(n), and auxiliary space remains O(n).

Can you reduce the extra memory used by the HashSet?

If modifying or reordering the input is allowed, a sorting-based approach can be considered. After sorting, duplicate values become adjacent, so one linear scan can detect a repeat. Sorting costs O(n log n) time, compared with O(n) expected time for the HashSet approach. The exact auxiliary space depends on the sorting implementation and data representation. The main tradeoff is potentially less extra storage at the cost of slower time and possible input reordering.

6. How would you schedule tasks with a priority queue?CodingMediumAmazon

Question Details

Schedule tasks by arrival and processing time using a min-heap to determine execution order.

Short Interview Answer (30-60 seconds)

I would sort the tasks by arrival time while keeping each original index. Then I would use a min heap for all tasks that have already arrived. The heap orders tasks by processing time, then by original index for ties. If the heap is empty, I jump the clock to the next arrival. I repeatedly run the heap's smallest task and advance the clock. The time complexity is O(n log n), and the auxiliary space is O(n).

Detailed Explanation

See the Code while reading this explanation.

Each task has an arrival time and a processing time. We have one CPU, so only one task can run at a time. When the CPU becomes free, we choose the shortest task that has already arrived. If two available tasks have the same processing time, we choose the smaller original index. If no task is available, we move the clock directly to the next arrival. The goal is to return the original task indices in the order that the CPU runs them.

Useful Questions to Ask the Interviewer
  1. Should ties in processing time be broken by the smaller original task index?
  2. If no task is available, should the CPU stay idle until the next arrival, allowing us to jump the clock directly to that arrival time?
  3. Should the returned array contain original task indices rather than the task values?
How would you schedule tasks with a priority queue? diagram
How to Explain It in an Interview
1. Understand the input and required output

Each input task is [arrivalTime, processingTime]. I add its original index so each record becomes [arrival, processing, index]. The output is the order of those original indices. For the diagram example, the input is [[1,2],[2,4],[3,2],[4,1]], and the returned order is [0, 2, 3, 1].

2. Choose the algorithm and data structure

First, sort the task records by arrival time. This lets us add newly available tasks in order without repeatedly searching the full input. Keep a Java PriorityQueue as a min heap. The heap contains tasks that have arrived but are not finished. It orders them first by smaller processing time and then by smaller original index. The key invariant is that before choosing the next task, the heap contains exactly the tasks that have arrived and have not yet been processed.

3. Initialize the state

Start with currentTime = 0, i = 0, an empty min heap, and an empty result array. The variable i points to the next task in arrival-sorted order. The heap is empty because no task has been added yet.

4. Walk through the example

At time 0, the heap is empty and the next task arrives at time 1. We jump currentTime to 1. T0 is now available, so we push it into the heap. T0 has processing time 2 and index 0. We pop T0, append index 0, and advance time to 3. The result is [0].

At time 3, T1 and T2 have both arrived. T1 has processing time 4 and index 1. T2 has processing time 2 and index 2. Both are pushed into the heap. T2 is at the top because processing time 2 is smaller than 4. We run T2, advance time from 3 to 5, and the result becomes [0, 2].

At time 5, T3 has arrived. T1 is still waiting. T3 has processing time 1, so the heap chooses T3 before T1. We run T3, advance time from 5 to 6, and the result becomes [0, 2, 3].

At time 6, only T1 remains. We run it for 4 units, so time becomes 10. The final result is [0, 2, 3, 1].

5. Explain why the result is correct

Tasks are inserted into the heap as soon as their arrival time is less than or equal to currentTime. Therefore, the heap contains exactly the runnable tasks when we make each scheduling decision. The heap exposes the smallest processing time. Its comparator uses the smaller original index when processing times are equal. This matches the scheduling rule at every decision point.

6. Explain the Java implementation

The code first copies every task into a three-value record containing arrival time, processing time, and original index. It sorts those records by arrival time. The PriorityQueue comparator uses processing time first and index second. The main loop continues until every task has been added and the heap is empty. When the heap is empty, the code jumps currentTime to the next arrival. It then pushes every task that has arrived, polls the next task, stores its original index, and adds its processing time to the clock.

7. Explain complexity and edge cases

Sorting takes O(n log n). Every task enters the heap once and leaves the heap once. Those heap operations take O(n log n) in total. The auxiliary space is O(n). Important cases are several tasks arriving at the same time, equal processing times, an idle gap before the next arrival, and an input containing only one task.

Key Insight / Why This Solution Works

The key idea is to separate task availability from task selection. Sorting by arrival time tells us when tasks become available. The min heap tells us which available task should run next. Each stored task record keeps [arrivalTime, processingTime, originalIndex], while the heap comparator orders records by processing time and then original index. The central invariant is: before each task is chosen, the heap contains exactly the tasks that have arrived and have not yet been processed. Because the heap exposes the shortest eligible task and uses the required tie-breaker, every scheduling decision follows the required rule.

Code
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Main {

    public static int[] getOrder(int[][] tasks) {
        int n = tasks.length;

        // Keep each task's original index before sorting by arrival time.
        // Each record is [arrivalTime, processingTime, originalIndex].
        int[][] indexed = new int[n][3];
        for (int i = 0; i < n; i++) {
            indexed[i][0] = tasks[i][0];
            indexed[i][1] = tasks[i][1];
            indexed[i][2] = i;
        }

        // Sort by arrival time so new runnable tasks can be added in order.
        Arrays.sort(indexed, Comparator.comparingInt(a -> a[0]));

        // Min heap containing tasks that have arrived but are not finished.
        // Smaller processing time wins. A smaller original index breaks ties.
        PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> {
            if (a[1] != b[1]) {
                return Integer.compare(a[1], b[1]);
            }
            return Integer.compare(a[2], b[2]);
        });

        int[] order = new int[n];
        long currentTime = 0;
        int i = 0;
        int out = 0;

        // Continue until every task has been added and every queued task has run.
        while (i < n || !minHeap.isEmpty()) {
            // If nothing is runnable, jump directly to the next task's arrival.
            if (minHeap.isEmpty() && currentTime < indexed[i][0]) {
                currentTime = indexed[i][0];
            }

            // Add every task that has arrived by the current CPU time.
            while (i < n && indexed[i][0] <= currentTime) {
                minHeap.offer(indexed[i]);
                i++;
            }

            // Poll the shortest available task using index as the tie-breaker.
            int[] task = minHeap.poll();

            // Record the original task index in execution order.
            order[out++] = task[2];

            // The CPU runs this task to completion before selecting another task.
            currentTime += task[1];
        }

        // All tasks have been processed in the required scheduling order.
        return order;
    }

    public static void main(String[] args) {
        // Run the exact example used in the approved diagram.
        int[][] tasks = { { 1, 2 }, { 2, 4 }, { 3, 2 }, { 4, 1 } };

        int[] order = getOrder(tasks);

        // Expected output: [0, 2, 3, 1]
        System.out.println(Arrays.toString(order));
    }
}
Time & Space Complexity

Let n be the number of tasks. Sorting the tasks by arrival time costs O(n log n). Each task is pushed into the min heap once and popped once. A heap push or pop costs O(log n), so all heap work is O(n log n). The total time is O(n log n). Auxiliary space is O(n) because the indexed task records and the heap can both grow with the number of tasks.

Where it is used

This pattern is useful when work becomes available over time and a system must repeatedly choose the best currently available job. Examples include single-worker job schedulers, CPU-style task simulations, background job processors, and request queues where the shortest available job should run first with deterministic tie-breaking.

Why Interviewers Ask This

This question checks whether you can combine sorting with a priority queue while tracking time correctly. The interviewer can see whether you preserve original indices, handle idle CPU periods, design the correct heap comparator, and add only tasks that have actually arrived. It also tests whether your Java code matches your explanation and whether you correctly include both sorting and heap operations when giving O(n log n) time and O(n) auxiliary space.

Common interview mistakes

A common mistake is sorting tasks without keeping their original indices, which makes the returned order wrong. Another mistake is adding future tasks to the heap before their arrival time. Candidates also forget to jump currentTime when the heap is empty. The heap comparator must use processing time first and original index second. Another common error is using only heap complexity and forgetting that sorting also costs O(n log n).

Interview tip

State the heap invariant before writing code: the min heap contains exactly the tasks that have arrived but have not been processed. Then explain that sorting controls when tasks enter the heap, while the comparator controls which available task leaves next.

Interviewer may ask next
What changes if the tasks are already sorted by arrival time?

We can skip the initial sorting step because the pointer can already process arrivals in the required order. We still add every available task to the same min heap and poll by processing time, then by original index. The invariant is unchanged because the heap still contains exactly the runnable tasks. Heap operations take O(n log n) total time, so the overall worst-case time remains O(n log n). Auxiliary space remains O(n). The main benefit is removing the separate sorting work.

How would this approach work if tasks arrived as a stream instead of being known in advance?

If tasks arrive in chronological order as a stream, we do not need the initial sort. As each task becomes available, we add it to the same min heap. Whenever the CPU becomes free, we poll the shortest available task and use the original index to break ties. The heap invariant remains the same: it contains arrived but unfinished tasks. For n tasks, heap operations take O(n log n) total time and the worst-case auxiliary space is O(n). The tradeoff is that future arrivals are unknown, so they cannot be preprocessed.

7. How would you randomly choose 10 items with equal probability?CodingMediumAmazon

Question Details

Choose 10 items from a list of 10k items so each item has an equal chance of being selected.

Short Interview Answer (30-60 seconds)

I would use reservoir sampling with a reservoir of size 10. I first copy the first 10 items into it. For each later item at index i, I draw a random j from 0 through i. If j is less than 10, I replace reservoir[j] with the current item. This keeps every processed position equally likely to remain in the sample. It uses O(n) expected time and O(k) auxiliary space, where k is 10.

Detailed Explanation

See the Code while reading this explanation.

We have 10,000 items and need to choose 10 of them fairly. Every input position must have the same chance of appearing in the final sample. We do not need to shuffle the whole list. Reservoir sampling keeps only k selected items while moving through the input from left to right. We first fill the reservoir. Then every later item gets a fair chance to replace one of those k items. This gives the required uniform sample with only k extra storage.

Useful Questions to Ask the Interviewer
  1. Should I return the selected values rather than their original indices?
  2. Can I assume the input contains at least 10 items?
  3. Is a uniformly random sample without replacement the expected result?
How would you randomly choose 10 items with equal probability? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input contains 10,000 items. The output must contain 10 selected items. Every input position must have the same probability of being included. In the full problem, that probability is 10/10000 = 1/1000 = 0.1%.

The diagram uses a smaller example so every step is visible. The example is [A, B, C, D, E, F, G, H] with k = 3. One valid random execution returns [E, D, H]. Every one of the eight input positions has final inclusion probability 3/8.

2. Choose reservoir sampling

I use reservoir sampling. The reservoir is a list that stores the current sample of size k. In the real problem, k = 10. In the example, k = 3.

The central invariant is: after processing index i, the reservoir is a uniform random sample of size k from items[0..i]. This means every processed position has probability k/(i+1) of being in the reservoir.

3. Initialize the state

For the example, k = 3. I copy the first three items into the reservoir, so the starting state is [A, B, C]. Traversal then starts at index 3, which contains D.

For the full problem, I copy the first 10 items into the reservoir and begin processing later items at index 10.

4. Walk through the exact example

At index 3, the current item is D. The fixed random draw is j = 1. The reservoir before the step is [A, B, C]. Since 1 < 3, D replaces slot 1. The reservoir becomes [A, D, C].

At index 4, the current item is E. The fixed random draw is j = 0. The reservoir before the step is [A, D, C]. Since 0 < 3, E replaces slot 0. The reservoir becomes [E, D, C].

At index 5, the current item is F. The fixed random draw is j = 4. Since 4 >= 3, F is skipped. The reservoir stays [E, D, C].

At index 6, the current item is G. The fixed random draw is j = 6. Since 6 >= 3, G is skipped. The reservoir stays [E, D, C].

At index 7, the current item is H. The fixed random draw is j = 2. Since 2 < 3, H replaces slot 2. The reservoir becomes [E, D, H].

All items have now been processed. The returned sample is [E, D, H]. This is one valid uniformly sampled result, not the only possible result.

5. Explain why it is correct

When processing index i, j is chosen uniformly from 0 through i. Therefore, the new item enters the reservoir exactly when j < k, which happens with probability k/(i+1).

If the new item enters, j also chooses one of the k reservoir slots uniformly. This replacement probability reduces the survival probability of each earlier item by exactly the right amount. By induction, after every processed index, each processed position has the same probability k/(i+1) of being in the reservoir.

6. Explain the Java implementation

The Java implementation validates the input, creates an ArrayList reservoir, and copies the first k items into it. It then processes each later index i. For the normal method, ThreadLocalRandom.current().nextInt(i + 1) generates j from 0 through i. If j < k, reservoir.set(j, items.get(i)) replaces that slot. Otherwise the reservoir does not change.

The executable example uses the exact fixed draws from the diagram: 1, 0, 4, 6, and 2. That makes the example reproducible and produces the exact diagram result [E, D, H]. The production overload uses ThreadLocalRandom and therefore may return a different valid sample on each run.

7. Explain complexity and edge cases

The algorithm processes each item at most once, so it uses O(n) expected time under the indexed-access list behavior shown in the diagram. It stores k sampled items, so auxiliary space is O(k). For this problem, k = 10.

If k = 0, the result is empty. If k equals the input size, every item is returned. Duplicate values are fine because input positions are processed independently. If the input contains fewer than k items, the implementation rejects it as invalid input.

Key Insight / Why This Solution Works

The key idea is to maintain a fair sample without shuffling the whole input. Start by copying the first k items into a reservoir. For every later item at index i, choose j uniformly from 0 through i. The item enters the sample only when j < k, and then it replaces reservoir[j]. The invariant is that after processing index i, the reservoir is a uniform random sample of size k from items[0..i]. The current item enters with probability k/(i+1), while uniform replacement keeps the same final inclusion probability for earlier items.

Code
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class Main {

    public static <T> List<T> chooseRandomK(List<T> items, int k) {
        // Reject an input that cannot produce a sample of exactly k items.
        if (items == null || k < 0 || items.size() < k) {
            throw new IllegalArgumentException("Invalid input");
        }

        // Seed the reservoir with the first k items.
        List<T> reservoir = new ArrayList<>(k);
        for (int i = 0; i < k; i++) {
            reservoir.add(items.get(i));
        }

        // Process every later item at most once.
        for (int i = k; i < items.size(); i++) {
            // nextInt(i + 1) returns a value from 0 through i, inclusive.
            int j = ThreadLocalRandom.current().nextInt(i + 1);

            // The current item enters only if j selects one of the k reservoir slots.
            if (j < k) {
                reservoir.set(j, items.get(i));
            }
        }

        // After all items are processed, the reservoir is the random sample.
        return reservoir;
    }

    private static <T> List<T> chooseRandomKWithFixedDraws(List<T> items, int k, int[] draws) {
        // This helper reproduces the exact audited diagram walkthrough.
        if (
            items == null ||
            draws == null ||
            k < 0 ||
            items.size() < k ||
            draws.length != items.size() - k
        ) {
            throw new IllegalArgumentException("Invalid input");
        }

        // Start with the same initial reservoir as the normal algorithm.
        List<T> reservoir = new ArrayList<>(k);
        for (int i = 0; i < k; i++) {
            reservoir.add(items.get(i));
        }

        // Apply the exact j values shown in the diagram in the same index order.
        for (int i = k; i < items.size(); i++) {
            int j = draws[i - k];

            // Each supplied draw must be valid for the required range [0, i].
            if (j < 0 || j > i) {
                throw new IllegalArgumentException("Invalid fixed draw");
            }

            // Use the same reservoir replacement rule as the production method.
            if (j < k) {
                reservoir.set(j, items.get(i));
            }
        }

        return reservoir;
    }

    public static void main(String[] args) {
        // Use the exact example input shown in the audited diagram.
        List<String> items = List.of("A", "B", "C", "D", "E", "F", "G", "H");
        int k = 3;

        // These are the diagram's fixed draws for D, E, F, G, and H.
        int[] diagramDraws = { 1, 0, 4, 6, 2 };

        // Reproduce the exact audited walkthrough and final reservoir.
        List<String> sample = chooseRandomKWithFixedDraws(items, k, diagramDraws);

        // Exact output for the diagram run: [E, D, H]
        System.out.println(sample);
    }
}
Time & Space Complexity

Let n be the number of input items and k be the sample size. We process each item at most once, so the diagram's algorithm uses O(n) expected time when indexed access to the input list is O(1), as it is for the example and an ArrayList-style input. The reservoir stores exactly k selected items, so auxiliary space is O(k). In this interview problem, n = 10,000 and k = 10, so the extra sample storage contains only 10 items.

Where it is used

Reservoir sampling is useful when software needs a fair random sample from a large list or a stream without shuffling or copying the whole input. For example, a system can sample events from a long log stream while keeping only k selected events in memory.

Why Interviewers Ask This

This question checks whether the candidate recognizes reservoir sampling instead of doing unnecessary full-list shuffling. It also tests probability reasoning, especially why the current item enters with probability k/(i+1) and why earlier items keep the same inclusion probability. The interviewer can evaluate whether the candidate writes the random range correctly in Java, maintains the reservoir invariant, handles k-related edge cases, and explains the O(n) expected-time and O(k) auxiliary-space behavior accurately.

Common interview mistakes

A common mistake is shuffling all 10,000 items even though only k sampled items are needed. Another mistake is drawing j from the wrong range. It must be uniform from 0 through the current index i. A candidate may also replace a reservoir item on every iteration instead of replacing only when j < k. Another mistake is claiming O(1) auxiliary space even though the reservoir stores k items. It is also wrong to claim that [E, D, H] is the only possible answer. It is only the result of the fixed random run shown in the diagram.

Interview tip

State the invariant before writing the loop: after processing index i, the reservoir is a uniform sample of size k from items[0..i]. Then explain that j < k happens with probability k/(i+1). This connects the random draw directly to the correctness proof.

Interviewer may ask next
How would this work if the items arrived as a stream and you did not know the final number of items?

The algorithm works almost unchanged. I fill the reservoir with the first k streamed items. For each later item with zero-based index i, I draw j uniformly from 0 through i. If j < k, I replace reservoir[j]. The same invariant holds after every arrival, so the reservoir is always a uniform sample of all items seen so far. For n received items, the time is O(n) expected and the auxiliary space is O(k). The main benefit is that I never need to store the entire stream.

Can the auxiliary space be reduced below O(k)?

Not if the method must return k selected values and those values must be kept before the result is returned. The reservoir itself contains k items, so it requires O(k) storage. Reservoir sampling already avoids extra memory that grows with n. The algorithm still processes the input in O(n) expected time. The tradeoff is that it keeps only the current sample instead of a shuffled or copied version of the full input.

8. How would you find a, b, and c such that a + b = c?CodingMediumAmazon

Question Details

Given an array of numbers, find a triple where two values sum to a third value.

Short Interview Answer (30-60 seconds)

I would first count how many times each value appears using a HashMap. Then I would try every pair of distinct indices i and j. For each pair, I compute c = a + b using long to avoid integer overflow. I check whether the array contains enough occurrences of c to use a, b, and c as three distinct elements. I return as soon as I find a valid triple. The expected time is O(n²), with O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The input is an array of numbers. We need to return three values from three distinct array positions where the first two values add up to the third. The third value can appear anywhere in the array, and values may be negative, zero, or repeated. I first count how many times every value appears. Then I try each pair of different positions and calculate their sum. The stored counts tell me whether that sum is available as a separate third element.

Useful Questions to Ask the Interviewer
  1. Should a, b, and c come from three distinct array positions?
  2. Can the array contain negative numbers, zero, and duplicate values?
  3. What should I return if no valid triple exists?
How would you find a, b, and c such that a + b = c? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array. The output is one valid triple of values [a, b, c] where a + b = c. The three values must come from three distinct array elements. For the example [1, 2, 4, 8, 10], one valid answer is [2, 8, 10] because 2 + 8 = 10.

2. Choose the algorithm and data structure

I use a HashMap<Integer, Integer> as a frequency map. A frequency map stores each array value as a key and the number of times that value appears as its value. Then I examine every pair of distinct indices i < j. For each pair, I compute c = a + b and use the frequency map to check whether enough copies of c exist.

The key invariant is that i and j always refer to two different elements. The frequency check then makes sure a third distinct array element can supply c. If c equals a or b, we require an extra occurrence. If c equals both a and b, we require three occurrences.

3. Initialize the state

For nums = [1, 2, 4, 8, 10], the frequency map is {1→1, 2→1, 4→1, 8→1, 10→1}. Pair traversal begins with i = 0 and j = 1. For each pair, a = nums[i] and b = nums[j]. I calculate the sum using long so adding two int values cannot overflow before I check the range.

4. Walk through the example

Step 1 uses i = 0 and j =

  1. So a = 1 and b =
  2. The sum is
  3. frequency[3] is 0, so processing continues.

Step 2 uses i = 0 and j = 2. The sum is 1 + 4 = 5. frequency[5] is 0, so processing continues.

Step 3 uses i = 0 and j = 3. The sum is 1 + 8 = 9. frequency[9] is 0, so processing continues.

Step 4 uses i = 0 and j = 4. The sum is 1 + 10 = 11. frequency[11] is 0, so processing continues.

Step 5 uses i = 1 and j = 2. The sum is 2 + 4 = 6. frequency[6] is 0, so processing continues.

Step 6 uses i = 1 and j = 3. Now a = 2 and b = 8. The sum is 10. Because 10 differs from both selected values, only one occurrence of 10 is required. frequency[10] is 1, so the algorithm returns [2, 8, 10] and stops immediately. The answer is found after 6 pair checks.

5. Explain why the result is correct

Every possible pair of distinct indices i < j is considered until a valid result is found. For each pair, c is calculated as exactly a + b. The frequency map verifies that enough occurrences of c exist to provide a separate third element. This also handles cases where c has the same value as a or b. Therefore every returned triple uses three distinct array elements and satisfies a + b = c.

6. Explain the Java implementation

The code first rejects null arrays and arrays with fewer than three elements. It builds the frequency map in one pass. Two nested loops then enumerate every pair i < j. The sum is calculated as long and skipped if it is outside the int range. The code calculates how many copies of c are required. If the map contains at least that many copies, it returns [a, b, c]. If no pair works, it returns null.

7. Explain complexity and edge cases

Building the frequency map takes expected O(n) time. There are O(n²) index pairs. Each HashMap lookup is O(1) on average, so the overall expected time is O(n²). The map can store up to n distinct values, so auxiliary space is O(n). Important cases are arrays shorter than three elements, negative values, zero, and duplicates. For example, [0, 0, 0] works because the map confirms that three occurrences of 0 exist.

Key Insight / Why This Solution Works

The key idea is to separate two jobs. First, count how many times every value appears. Second, enumerate every pair of distinct indices i < j and ask whether their sum exists as a third element. The HashMap stores value → occurrence count. For each pair, c = a + b. The central invariant is that i and j already consume two distinct array elements. The frequency check therefore requires enough copies of c to provide a third distinct element. This handles negative numbers, zero, and duplicates without making an ordering assumption about where c appears.

Code
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static int[] findTriple(int[] nums) {
        // A valid triple needs three array elements.
        if (nums == null || nums.length < 3) {
            return null;
        }

        // Store value -> number of occurrences so duplicate values are handled correctly.
        Map<Integer, Integer> frequency = new HashMap<>();
        for (int value : nums) {
            frequency.merge(value, 1, Integer::sum);
        }

        // Enumerate every pair of distinct indices exactly once with i < j.
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                int a = nums[i];
                int b = nums[j];

                // Use long so adding two int values cannot overflow before validation.
                long sum = (long) a + b;

                // c must fit in int because every possible third value comes from int[].
                if (sum < Integer.MIN_VALUE || sum > Integer.MAX_VALUE) {
                    continue;
                }

                int c = (int) sum;

                // One copy of c is normally needed for the third element.
                // If c equals a or b, the selected elements already consume copies of c.
                int required = 1;
                if (c == a) {
                    required++;
                }
                if (c == b) {
                    required++;
                }

                // Return only if enough occurrences exist to use three distinct elements.
                if (frequency.getOrDefault(c, 0) >= required) {
                    return new int[] { a, b, c };
                }
            }
        }

        // No valid triple was found after all distinct index pairs were checked.
        return null;
    }

    public static void main(String[] args) {
        // Run the same example shown in the approved diagram.
        int[] nums = { 1, 2, 4, 8, 10 };
        int[] result = findTriple(nums);

        // Print the returned triple in the same value format as the diagram.
        if (result != null) {
            System.out.println("[" + result[0] + ", " + result[1] + ", " + result[2] + "]");
        } else {
            System.out.println("null");
        }
    }
}
Time & Space Complexity

Let n be the number of elements. Building the frequency map takes expected O(n) time because Java HashMap insertion is O(1) on average. The two nested loops examine O(n²) pairs. Each frequency lookup is O(1) on average, so the total expected time is O(n²). The HashMap may contain up to n different values, so the auxiliary space is O(n). Auxiliary space means extra memory used by the algorithm.

Where it is used

This pattern is useful when a problem asks whether a value calculated from a pair also exists somewhere in the same collection. A frequency map is especially useful when duplicates matter because it records how many copies of each value exist. Similar counting-and-lookup patterns are used in sum problems, inventory matching, and validation tasks where a calculated value must be backed by a separate stored item.

Why Interviewers Ask This

This question tests whether you can turn a simple equation into a correct search strategy. The interviewer can see whether you choose an appropriate data structure, handle duplicate values correctly, keep array positions distinct, reason about integer overflow, and stop as soon as a valid answer is found. It also tests whether you can explain the frequency-count invariant and state expected HashMap performance and overall complexity accurately.

Common interview mistakes

A common mistake is checking only whether c exists without checking how many copies are available. If c equals a or b, a separate occurrence is needed for the third element. Another mistake is allowing i and j to refer to the same array position. Candidates may also forget integer overflow when calculating a + b with int values. Another error is claiming guaranteed O(1) HashMap operations instead of average O(1). Finally, do not keep processing pairs after a valid triple has been returned.

Interview tip

Explain the occurrence-count rule before writing the nested loops. Say that i and j already consume two elements, so the map must prove that enough copies of c exist to supply a third distinct element. Using [0, 0, 0] as a quick duplicate example makes this rule easy to explain.

Interviewer may ask next
Can auxiliary space be reduced?

Yes, but there is a tradeoff. Without the frequency map, we could examine a third index for every pair. That uses O(1) auxiliary space but takes O(n³) time. The current frequency-map solution uses O(n) extra space to keep the expected running time at O(n²). Correctness is preserved because the third index would still be required to differ from i and j.

What changes if duplicate values exist?

The current algorithm already handles duplicates because the HashMap stores occurrence counts. For each pair, required starts at 1 for c. If c equals a, required increases by one. If c also equals b, it increases again. For example, [0, 0, 0] needs frequency[0] >= 3. The expected time remains O(n²), and the auxiliary space remains O(n).

9. How would you merge multiple huge sorted files on disk?CodingHardAmazon

Question Details

Merge multiple large sorted files when the data does not fit in memory.

Short Interview Answer (30-60 seconds)

I would use a k-way merge with a min heap. I open each sorted file with a BufferedReader, read one current value from every non-empty file, and put those values into a Java PriorityQueue. I repeatedly remove the smallest value, write it to the output file, and read the next value only from that same source file. The heap always contains the smallest unread value from each active file. The time complexity is O(N log k), and the auxiliary space is O(k) plus small I/O buffers.

Detailed Explanation

See the Code while reading this explanation.

The problem gives several very large files that are already sorted. The files are too large to load fully into memory. We need to create one new file containing all records in sorted order. The main idea is to keep only one current value from each file in memory. A min heap tells us which current value is smallest. After writing that value, we read only the next value from the same file. This keeps memory usage small while still producing the correct sorted output.

Useful Questions to Ask the Interviewer
  1. Is every input file already sorted in ascending order?
  2. Can I assume each record contains one integer per line, as in the example?
  3. Can all input files remain open at the same time, or can k exceed the operating system open-file limit?
How would you merge multiple huge sorted files on disk? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is k individually sorted files stored on disk. The complete data does not fit in memory. The output is one file containing every input value in ascending order. For the diagram example, File A is [1, 4, 9], File B is [2, 6, 8], and File C is [3, 5, 7, 10]. The final output is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

2. Choose the algorithm and data structure

I use a k-way merge. The data structure is a min heap. In Java, PriorityQueue is a min heap when its ordering puts the smallest value first. Each heap entry stores two things: the current value and the file index that produced it. The central invariant is: the heap contains the smallest unread value from each non-empty file. Because of that invariant, the smallest value in the heap is also the smallest value that can appear next in the final output.

3. Initialize the state

I open one BufferedReader for each input file and one BufferedWriter for the output file. I read the first value from every non-empty file. For the example, the first values are A:1, B:2, and C:3. I insert heap entries (1,A), (2,B), and (3,C). The output is initially empty.

4. Walk through the example

Step 1: The heap contains [(1,A), (2,B), (3,C)]. I remove (1,A), write 1, then read the next value from File A, which is 4. I insert (4,A). The heap becomes [(2,B), (3,C), (4,A)].

Step 2: I remove (2,B), write 2, read 6 from File B, and insert (6,B). The heap becomes [(3,C), (4,A), (6,B)].

Step 3: I remove (3,C), write 3, read 5 from File C, and insert (5,C). The heap becomes [(4,A), (5,C), (6,B)].

Step 4: I remove (4,A), write 4, read 9 from File A, and insert (9,A). The heap becomes [(5,C), (6,B), (9,A)].

Step 5: I remove (5,C), write 5, read 7 from File C, and insert (7,C). The heap becomes [(6,B), (7,C), (9,A)].

Step 6: I remove (6,B), write 6, read 8 from File B, and insert (8,B). The heap becomes [(7,C), (8,B), (9,A)].

Step 7: I remove (7,C), write 7, read 10 from File C, and insert (10,C). The heap becomes [(8,B), (9,A), (10,C)].

Step 8: I remove (8,B) and write 8. File B has reached EOF, so I do not insert another heap entry. The heap becomes [(9,A), (10,C)].

Step 9: I remove (9,A) and write 9. File A has reached EOF, so I do not insert another heap entry. The heap becomes [(10,C)].

Step 10: I remove (10,C) and write 10. File C has reached EOF. The heap becomes empty, so processing stops. The merged output is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

5. Explain why the result is correct

Before every output step, the heap contains the smallest unread value from each file that still has data. Therefore, any unread value that could be written next is either already in the heap or comes after one of those heap values in its own sorted file. The minimum heap entry is therefore the global smallest unread value. After writing it, I refill only from that same file, which restores the invariant. Repeating this until the heap is empty produces a fully sorted output.

6. Explain the Java implementation

The Java code keeps a BufferedReader for every input file. HeapNode stores a value and the index of its source reader. Initialization reads one value from every non-empty file and inserts it into PriorityQueue. The main loop polls the minimum node, writes its value, then reads one new value from the same reader. If that reader still has data, the new value goes back into the heap. When the heap is empty, every file is exhausted. BufferedReader and BufferedWriter let the program stream data instead of loading complete files into memory.

7. Explain complexity and edge cases

Let N be the total number of records across all files and k be the number of files. Every record enters and leaves a heap whose size is at most k, so the time complexity is O(N log k). The heap stores at most one current record per non-empty file, so auxiliary memory is O(k), plus the small I/O buffers used by the readers and writer. Empty files are skipped. Duplicate and negative values work naturally. A single input file also works. If k is larger than the operating system open-file limit, the files must be merged in batches across multiple passes.

Key Insight / Why This Solution Works

The key idea is to avoid loading the huge files into memory. Because every file is already sorted, I only need to know the smallest unread value from each file. I keep those current values in a min heap. Each heap entry stores the value and the file it came from. The invariant is that the heap contains the smallest unread value from every non-empty file. The heap minimum is therefore the next global value to write. After removing it, only that same source file needs to provide a replacement. This is the k-way merge pattern shown in the diagram and is well suited to sorted data stored on disk.

Code
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

public class Main {

    static class HeapNode {

        int value;
        int fileIndex;

        HeapNode(int value, int fileIndex) {
            this.value = value;
            this.fileIndex = fileIndex;
        }
    }

    public static void mergeSortedFiles(List<Path> inputFiles, Path outputFile) throws IOException {
        // Keep one reader per input file so we can advance only the file
        // that produced the current minimum value.
        List<BufferedReader> readers = new ArrayList<>();

        // Keep the smallest unread value from each active file in a min heap.
        PriorityQueue<HeapNode> minHeap = new PriorityQueue<>(
            Comparator.comparingInt(node -> node.value)
        );

        try (BufferedWriter writer = Files.newBufferedWriter(outputFile)) {
            // Read the first value from every file to build the initial heap.
            for (int i = 0; i < inputFiles.size(); i++) {
                BufferedReader reader = Files.newBufferedReader(inputFiles.get(i));
                readers.add(reader);

                Integer firstValue = readNextInt(reader);

                // An empty file has no current head, so it contributes no heap item.
                if (firstValue != null) {
                    minHeap.offer(new HeapNode(firstValue, i));
                }
            }

            // Poll one global minimum at a time until every file is exhausted.
            while (!minHeap.isEmpty()) {
                HeapNode node = minHeap.poll();

                // Write the smallest current value to the merged output file.
                writer.write(Integer.toString(node.value));
                writer.newLine();

                // Only the source file of the removed value can expose
                // a new candidate, so advance that reader exactly once.
                Integer nextValue = readNextInt(readers.get(node.fileIndex));

                // If the source file still has data, restore the heap invariant
                // by adding its new smallest unread value.
                if (nextValue != null) {
                    minHeap.offer(new HeapNode(nextValue, node.fileIndex));
                }
            }
        } finally {
            // Close every input reader even if reading, parsing, or writing fails.
            for (BufferedReader reader : readers) {
                if (reader != null) {
                    reader.close();
                }
            }
        }
    }

    private static Integer readNextInt(BufferedReader reader) throws IOException {
        // Read one record at a time so the complete file is never loaded into memory.
        String line = reader.readLine();

        // null means EOF. Otherwise parse this line as the next integer record.
        return line == null ? null : Integer.parseInt(line.trim());
    }

    public static void main(String[] args) throws IOException {
        Path directory = Files.createTempDirectory("sorted-file-merge");
        Path fileA = directory.resolve("A.txt");
        Path fileB = directory.resolve("B.txt");
        Path fileC = directory.resolve("C.txt");
        Path outputFile = directory.resolve("merged.txt");

        // Create the exact three sorted input files used in the diagram example.
        Files.write(fileA, List.of("1", "4", "9"));
        Files.write(fileB, List.of("2", "6", "8"));
        Files.write(fileC, List.of("3", "5", "7", "10"));

        // Run the same k-way min-heap merge shown in the diagram.
        mergeSortedFiles(List.of(fileA, fileB, fileC), outputFile);

        // Read and print the final merged file for the example runner.
        try (BufferedReader reader = Files.newBufferedReader(outputFile)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
Time & Space Complexity

Let N be the total number of records in all files, and let k be the number of input files. Each record is inserted into and removed from a min heap whose size is at most k. A heap operation costs O(log k), so the total time is O(N log k). The heap keeps at most one current value for each file, so auxiliary space is O(k). The readers and writer also use small I/O buffers. The complete input data never needs to fit in RAM.

Where it is used

This pattern is useful when sorted data is too large for memory. It is used for external sorting, merging sorted log files, combining ordered data partitions, database processing, and large batch pipelines. The same k-way merge pattern can also work with comparable records when the program uses a suitable parser and comparator.

Why Interviewers Ask This

This question tests whether you can recognize a k-way merge and adapt it to data that lives on disk. The interviewer is checking whether you select a min heap, maintain the heap invariant, stream records instead of loading all files into RAM, keep each heap value linked to its source file, write correct Java I/O code, explain O(N log k) time and O(k) auxiliary space, and notice practical issues such as the operating system limit on simultaneously open files.

Common interview mistakes

A common mistake is loading all files into memory, which breaks the main requirement. Another mistake is placing many records from every file into the heap instead of keeping only one current value per active file. Candidates can also forget that after polling the minimum, the replacement must come from that same source file. Using max-heap ordering would produce the wrong output order. Another mistake is claiming O(N) time instead of O(N log k). Finally, opening too many files at once can exceed the operating system open-file limit.

Interview tip

State the invariant early: the PriorityQueue contains the smallest unread value from every non-empty file. Then explain that after polling one value, only that value's source file needs to advance. This makes the correctness argument and the O(N log k) complexity easy to explain.

Interviewer may ask next
What would you change if k is larger than the operating system open-file limit?

I would merge the files in batches. Each batch would contain only as many input files as can safely remain open. I would use the same k-way min-heap merge to create sorted intermediate files. Then I would merge those intermediate files in another pass and repeat until one final file remains. Correctness is preserved because every intermediate file is sorted before the next pass. If at most B files can be merged at once, there are about log base B of k merge passes. Each pass processes all N records and uses heap operations of O(log B), so the comparison work per pass is O(N log B). Auxiliary memory is O(B) plus I/O buffers. The tradeoff is extra temporary files and additional disk reads and writes across multiple passes.

How would the solution change if each file contained comparable records instead of integers?

The k-way merge structure stays the same. HeapNode would store a record instead of an int, and PriorityQueue would use a comparator for the required sort key. The reader would parse one record at a time, and the writer would serialize one record at a time. The invariant stays the same: the heap contains the smallest unread record from each non-empty file. The time complexity remains O(N log k), and auxiliary space remains O(k) heap entries plus I/O buffers. The main tradeoff is the additional cost of parsing, comparing, and writing larger records.

10. How would you find a word ladder between two words?CodingHardAmazon

Question Details

Find a valid sequence of dictionary words that changes one letter at a time from a start word to an end word.

Short Interview Answer (30-60 seconds)

I would model the words as an unweighted graph and use BFS. Each word is a node, and two words are connected when they differ by exactly one character. I keep a queue for BFS, a HashSet for visited words, and a parent map for rebuilding the path. I mark each word visited when I first discover it. I stop when I discover the end word. BFS gives a shortest ladder. The expected time is O(n × L × 26), and auxiliary space is O(n).

Detailed Explanation

See the Code while reading this explanation.

We need to build a chain from the start word to the end word. In each move, we can change only one letter. The new word must be in the given dictionary. For the example, we start with "hit" and want to reach "cog". A valid result is ["hit", "hot", "dot", "dog", "cog"]. I use BFS because it checks words in increasing number of changes. I remember where each discovered word came from so I can rebuild the final chain when "cog" is found.

Useful Questions to Ask the Interviewer
  1. Can I assume all usable words have the same length as beginWord and endWord?
  2. Should I return an empty list when endWord is not in the dictionary or no ladder can be found?
  3. Can beginWord and endWord be the same word?
How would you find a word ladder between two words? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input has a beginWord, an endWord, and a list of dictionary words. We need to return a sequence of words from beginWord to endWord. Every neighboring pair in the sequence must differ in exactly one character. Every intermediate word must belong to the dictionary.

The diagram uses this example: beginWord = "hit" endWord = "cog" wordList = ["hot", "dot", "dog", "lot", "cog"]

The returned sequence is ["hit", "hot", "dot", "dog", "cog"]. It has five words and four transformations.

2. Choose BFS and the supporting data structures

I treat every word as a node in an implicit unweighted graph. Two words have an undirected connection when they differ by exactly one character. I do not build all graph edges in advance. Instead, I generate possible one-letter changes when I process a word.

I use an ArrayDeque as the BFS queue. I use a HashSet called dict for average O(1) dictionary membership checks. I use another HashSet called visited so the same word is not added to the queue more than once. I use a HashMap<String, String> called parent. It stores child word → predecessor word. For example, parent["dog"] = "dot" means we first reached "dog" from "dot".

The central invariant is that every word already placed in the queue has a shortest known path from beginWord. BFS processes words in increasing number of transformations.

3. Initialize the BFS state

First, if beginWord equals endWord, I return [beginWord]. Next, I convert wordList to a HashSet. If endWord is not present, I return an empty list.

For the normal BFS path, the initial state is: queue = [hit] visited = {hit} parent = {hit → null}

The null parent marks the beginning of the reconstructed path.

4. Walk through the exact example

Step 0: The queue is [hit]. I dequeue "hit". I generate words by changing one position at a time with letters from 'a' through 'z'. The valid unseen dictionary neighbor is "hot". I store parent["hot"] = "hit", mark "hot" visited, and enqueue it. The queue becomes [hot].

Step 1: I dequeue "hot". Its new valid unseen neighbors are "dot" and "lot". I store parent["dot"] = "hot" and parent["lot"] = "hot". I mark both visited and enqueue them. The queue becomes [dot, lot].

Step 2: I dequeue "dot". The new valid unseen neighbor is "dog". I store parent["dog"] = "dot", mark it visited, and enqueue it. The queue becomes [lot, dog].

Step 3: I dequeue "lot". It produces no new unseen dictionary word. The queue becomes [dog]. Processing continues.

Step 4: I dequeue "dog". It discovers "cog". I store parent["cog"] = "dog" and mark it visited. Because "cog" is endWord, I stop BFS immediately. I do not process any later state.

The parent chain is cog ← dog ← dot ← hot ← hit. Following those links backward and reversing the chain gives [hit, hot, dot, dog, cog].

5. Explain why the result is correct

BFS explores an unweighted graph level by level. The first time a word is discovered, BFS has reached it with the fewest transformations from beginWord. We mark a word visited at first discovery, so a longer later route cannot replace that shortest predecessor. The parent map records the predecessor from that first discovery. Therefore, when endWord is first discovered, following the parent links produces a valid shortest ladder.

6. Explain the Java implementation

For each dequeued word, the code copies its characters into a char array. For every position, it tries letters 'a' through 'z'. It skips the original character because that would not create a change. Each generated word is checked against dict. If it is in the dictionary and visited.add(next) succeeds, this is its first discovery. The code stores its parent. If it equals endWord, the code immediately calls buildPath. Otherwise, it is added to the BFS queue. After one character position is processed, the original character is restored.

The buildPath method starts at endWord and repeatedly follows parent links. It adds every word to the front of a LinkedList, so the final list is already in beginWord-to-endWord order.

7. Explain complexity and edge cases

The diagram gives expected time O(n × L × 26), often written as O(nL), where n is the number of reachable words and L is the word length. HashSet and HashMap operations are O(1) on average. Auxiliary space is O(n) for the dictionary set, visited set, queue, and parent map.

Important edge cases are beginWord equal to endWord, endWord missing from the dictionary, duplicate dictionary words, and a disconnected dictionary with no reachable ladder. A HashSet automatically removes duplicate dictionary entries.

Key Insight / Why This Solution Works

The key idea is to view the problem as shortest-path search in an implicit unweighted graph. Each dictionary word is a node. Two nodes are connected when their words differ by exactly one character. BFS is a good fit because it processes nodes in increasing distance from beginWord. Instead of building every edge first, the algorithm generates one-letter mutations only when a word is dequeued. The queue stores words waiting to be processed. The visited set prevents repeated work. The parent map stores child word → predecessor word. The invariant is that every queued word already has a shortest known path from beginWord. Therefore, the first discovery of endWord gives a shortest ladder, and the parent links can reconstruct it.

Code
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

public class Main {

    public static void main(String[] args) {
        // Run the exact example shown in the diagram.
        String beginWord = "hit";
        String endWord = "cog";
        List<String> wordList = new ArrayList<>(List.of("hot", "dot", "dog", "lot", "cog"));

        // Find the ladder and print the returned sequence.
        List<String> ladder = findWordLadder(beginWord, endWord, wordList);
        System.out.println(ladder);
    }

    public static List<String> findWordLadder(
        String beginWord,
        String endWord,
        List<String> wordList
    ) {
        // If both words are already equal, no transformation is needed.
        if (beginWord.equals(endWord)) {
            return List.of(beginWord);
        }

        // Store dictionary words in a HashSet for average O(1) membership checks.
        // The set also removes duplicate dictionary entries.
        Set<String> dict = new HashSet<>(wordList);

        // We cannot reach endWord through dictionary words if it is not present.
        if (!dict.contains(endWord)) {
            return Collections.emptyList();
        }

        // The queue processes words in BFS order.
        Queue<String> queue = new ArrayDeque<>();

        // Mark words when they are first discovered so they are not enqueued again.
        Set<String> visited = new HashSet<>();

        // Store child word -> predecessor word so the final ladder can be rebuilt.
        Map<String, String> parent = new HashMap<>();

        // Initialize BFS from beginWord.
        queue.offer(beginWord);
        visited.add(beginWord);
        parent.put(beginWord, null);

        while (!queue.isEmpty()) {
            // Process the next word in BFS order.
            String current = queue.poll();
            char[] chars = current.toCharArray();

            // Try changing each character position independently.
            for (int i = 0; i < chars.length; i++) {
                char original = chars[i];

                // Generate every lowercase one-letter mutation for this position.
                for (char c = 'a'; c <= 'z'; c++) {
                    // Using the original character would not create a transformation.
                    if (c == original) {
                        continue;
                    }

                    chars[i] = c;
                    String next = new String(chars);

                    // Accept only dictionary words that have not been discovered before.
                    // visited.add(next) returns true only on the first discovery.
                    if (dict.contains(next) && visited.add(next)) {
                        // Record how this new word was reached.
                        parent.put(next, current);

                        // Stop immediately when endWord is first discovered.
                        // BFS first discovery gives a shortest ladder in this unweighted graph.
                        if (next.equals(endWord)) {
                            return buildPath(endWord, parent);
                        }

                        // Continue BFS from this newly discovered word later.
                        queue.offer(next);
                    }
                }

                // Restore the current word before mutating the next character position.
                chars[i] = original;
            }
        }

        // Defensive fallback for a disconnected dictionary or no reachable ladder.
        return Collections.emptyList();
    }

    private static List<String> buildPath(String endWord, Map<String, String> parent) {
        // Add words to the front while following parent links backward.
        LinkedList<String> path = new LinkedList<>();

        // The parent of beginWord is null, which ends reconstruction.
        for (String word = endWord; word != null; word = parent.get(word)) {
            path.addFirst(word);
        }

        // The path is now ordered from beginWord to endWord.
        return path;
    }
}
Time & Space Complexity

The expected time shown in the diagram is O(n × L × 26), often simplified to O(nL). Here, n is the number of reachable words and L is the word length. For every processed word, we try each of its L character positions and the 26 lowercase letters. HashSet and HashMap membership, insertion, and lookup are O(1) on average, so this is an expected-time bound rather than a guaranteed worst-case hashing bound. Auxiliary space is O(n). The dictionary set, visited set, BFS queue, and parent map can all grow with the number of words.

Where it is used

This BFS pattern is useful when software needs a shortest sequence of equal-cost transformations. Similar ideas appear in state-transition problems, puzzle solvers, and routing through unweighted states. The parent-map pattern is useful when BFS must return the actual path instead of only saying whether a destination is reachable.

Why Interviewers Ask This

This problem tests whether you can recognize an implicit graph without building every edge first. It also checks whether you know that BFS is appropriate for shortest paths in an unweighted graph. The interviewer can evaluate how you manage queue order, visited state, parent reconstruction, and early stopping. In Java, it also tests practical use of ArrayDeque, HashSet, and HashMap, plus whether you explain average hash-table complexity and edge cases accurately.

Common interview mistakes

A common mistake is marking a word visited only when it is removed from the queue. That can enqueue the same word more than once. Another mistake is forgetting to store the parent when a word is first discovered, which makes path reconstruction impossible. Candidates may also forget to restore the original character after testing mutations at one position. Another error is continuing BFS after endWord has already been discovered even though the diagram stops immediately. Finally, it is easy to claim guaranteed O(1) HashSet or HashMap operations instead of saying they are O(1) on average.

Interview tip

State the BFS invariant before coding: every word already placed in the queue has a shortest known path from beginWord. Then say that you mark a word visited when it is first discovered and save its parent at the same time. This makes both the shortest-path reasoning and the path reconstruction easy to explain.

Interviewer may ask next
Why should a word be marked visited when it is discovered instead of when it is removed from the BFS queue?

Marking it visited at discovery time prevents another word in the same or a later BFS level from adding the same word to the queue again. The first discovery is already through a shortest path because BFS processes states in increasing distance order. We also store the parent at that moment. This keeps one shortest predecessor for each discovered word, avoids duplicate queue entries, and preserves the expected O(n × L × 26) time and O(n) auxiliary space shown for this solution.

What happens if there is no valid ladder from beginWord to endWord?

The same BFS continues until the queue becomes empty. Every reachable dictionary word is processed at most once because visited prevents repeats. If endWord is never discovered, the method returns an empty list. No different algorithm is needed. The expected time remains O(n × L × 26), where n is the number of reachable words, and the auxiliary space remains O(n) for the dictionary set, visited set, queue, and parent map.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.