36 Apple Java Developer Interview Questions & Answers

apple icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

1. How would you solve Two Sum?CodingEasyApple

Question Details

Solve the Two Sum problem and explain your approach, edge cases, and time and space complexity.

Short Interview Answer (30-60 seconds)

I would use a HashMap that stores each earlier value and its index. I process the array from left to right. For each number, I calculate its complement, which is target minus the current value. I check the map before inserting the current value, so I cannot reuse the same element. When the complement is found, I return the earlier index and current index. This takes O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The problem gives an integer array and a target value. We need to find two different positions whose values add up to the target. The answer must contain the indices, not the values. I use a hash map because it lets me remember values that appeared earlier and quickly check whether the value needed to complete the target is already available. I move through the array from left to right and stop as soon as I find a valid pair.

Useful Questions to Ask the Interviewer
  1. Should I return the indices rather than the values?
  2. Is one valid pair enough if more than one pair could exist?
  3. What should the method return if no pair exists?
How would you solve Two Sum? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array called nums and an integer target. We must return two different indices [i, j] where nums[i] + nums[j] equals target. The diagram uses nums = [2, 7, 11, 15] and target = 9. The returned result is [0, 1] because nums[0] is 2 and nums[1] is 7.

2. Choose the HashMap approach

I use a HashMap named seen. Its key is an earlier value from the array. Its value is that number's earlier index. For each current number, I calculate complement = target - nums[i]. Complement means the number needed to reach the target. I check whether that complement is already in seen. The important invariant is that before processing index i, seen contains only values from earlier indices.

3. Initialize the state

Traversal starts at index 0. The map starts empty, so seen = {}. Nothing has been processed yet. As I move left to right, I add a value only after checking whether its complement is already present. This order prevents the current element from matching with itself.

4. Walk through the example

At index 0, the current value is 2. The complement is 9 - 2 = 7. The map is empty, so 7 is not present. I store 2 -> 0. The state changes from {} to {2 -> 0}, and processing continues.

At index 1, the current value is 7. The complement is 9 - 7 = 2. The state before this check is {2 -> 0}. The map contains 2, so I return [0, 1]. The map stays unchanged because the answer has been found. Processing stops immediately. Indices 2 and 3 are not processed.

5. Explain why the result is correct

The map contains only values from earlier indices. When I am at index 1 and find complement 2 in the map, that stored 2 came from index 0. Therefore the two indices are different. Their values are 2 and 7, and 2 + 7 = 9. So indices [0, 1] are the correct returned pair.

6. Explain the Java implementation

The code creates a HashMap<Integer, Integer>. It loops over nums from left to right. For every element, it calculates the complement and checks seen.containsKey(complement). If the key exists, it returns the stored earlier index together with the current index. If not, it stores nums[i] -> i. A defensive empty-array return is included only for the case where no pair is found.

7. Explain complexity and edge cases

HashMap lookup and insertion are O(1) on average, so the overall expected time is O(n). We process the input at most once and may stop early. The map can hold up to n entries, so auxiliary space is O(n). Duplicate values work. For example, [3, 3] with target 6 works because the first 3 is stored before the second 3 is checked. Negative numbers and zero also work. An array with fewer than two elements cannot form a pair.

Key Insight / Why This Solution Works

The key idea is to remember earlier values instead of searching the rest of the array again for every element. The HashMap stores value -> earlier index. For the current value nums[i], calculate complement = target - nums[i]. If the complement is already in the map, the stored index and i form the answer. Otherwise, store nums[i] -> i. The central invariant is that before processing index i, the map contains only values from earlier indices. Checking before insertion also prevents the same element from being used twice.

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

public class Main {

    public static int[] twoSum(int[] nums, int target) {
        // Store each earlier array value as the key and its index as the value.
        Map<Integer, Integer> seen = new HashMap<>();

        // Process elements from left to right and stop as soon as a valid pair is found.
        for (int i = 0; i < nums.length; i++) {
            // Complement means the value needed with nums[i] to reach the target.
            int complement = target - nums[i];

            // Check before insertion so the current element cannot be reused as its own partner.
            if (seen.containsKey(complement)) {
                // The complement belongs to an earlier index, so return that index and i.
                return new int[] { seen.get(complement), i };
            }

            // No pair has been found yet, so remember this value and its index for later checks.
            seen.put(nums[i], i);
        }

        // Defensive fallback when the input does not contain a valid pair.
        return new int[0];
    }

    public static void main(String[] args) {
        // Run the exact example used in the approved diagram.
        int[] nums = { 2, 7, 11, 15 };
        int target = 9;

        // Execute the algorithm and print the returned indices.
        int[] result = twoSum(nums, target);
        System.out.println(Arrays.toString(result));
    }
}
Time & Space Complexity

Expected time is O(n). We process the array at most once and stop when the pair is found. Each Java HashMap lookup and insertion is O(1) on average, with normal hashing and collision caveats. Auxiliary space is O(n) because the map may store up to one entry for each processed value. Auxiliary space means extra memory used by the algorithm.

Where it is used

This pattern is useful when a program needs to find a matching value quickly while processing data in one direction. It can be used for pair-matching problems, checking whether a related value appeared earlier, matching IDs, or remembering previously seen values and their positions.

Why Interviewers Ask This

This question checks whether you can recognize a lookup pattern and choose a suitable data structure. The interviewer can see whether you preserve original indices, calculate the complement correctly, handle duplicate values, and avoid reusing the same element. It also tests whether you can maintain a clear invariant, stop correctly after finding the answer, write correct Java HashMap code, and explain expected time and auxiliary space accurately.

Common interview mistakes

A common mistake is returning the values 2 and 7 instead of the required indices 0 and 1. Another mistake is inserting the current value before checking its complement, which can allow the same element to be reused. Candidates may also store index -> value instead of value -> earlier index. Another mistake is continuing to describe later indices as processed after the method has already returned. It is also incorrect to claim guaranteed O(n) time without noting that Java HashMap operations are O(1) on average.

Interview tip

Say the map meaning out loud before coding: value -> earlier index. Then explain that you check the complement before insertion. Those two points make the index handling, duplicate handling, and no-reuse rule easy to follow.

Interviewer may ask next
What happens if the array contains duplicate values, such as [3, 3] with target 6?

The same algorithm works. At index 0, the complement is 3, but the map is empty, so we store 3 -> 0. At index 1, the complement is again 3. The map already contains 3 -> 0, so we return [0, 1]. Because lookup happens before insertion, the two indices are different. The expected time remains O(n), and auxiliary space remains O(n).

What changes if no valid pair is guaranteed to exist?

The HashMap algorithm does not need to change. We still process each element from left to right, check the complement first, and then store the current value and index. If the loop finishes without finding a pair, the method can return an empty array, as shown by the defensive fallback. Expected time remains O(n), and auxiliary space remains O(n). The main tradeoff is that the caller must understand what an empty result means.

2. How would you solve Maximum Subarray?CodingEasyApple

Question Details

Find the maximum subarray sum and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would use Kadane’s algorithm. I keep currentSum as the best contiguous subarray sum ending at the current position, and maxSum as the best sum seen anywhere so far. I start both with nums[0], then move from index 1 to the end. At each value, I either start a new subarray or extend the previous one, whichever gives the larger sum. This works in O(n) time and uses O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The problem gives an integer array and asks for the largest sum we can get from one continuous part of that array. The numbers must stay next to each other, so we cannot skip elements. The main idea is to move from left to right and remember the best sum that can end at the current position. We also remember the best sum found anywhere so far. This avoids checking every possible continuous part of the array and gives a simple linear-time solution.

Useful Questions to Ask the Interviewer
  1. Can I assume the input array has at least one element?
  2. Do you only need the maximum sum, or should I also return the subarray itself?
How would you solve Maximum Subarray? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array called nums. The output is one integer: the maximum possible sum of a contiguous subarray. Contiguous means the chosen elements must be next to each other in the original array. For the example nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4], the answer is 6 because the contiguous subarray [4, -1, 2, 1] has sum 6.

2. Choose Kadane’s algorithm

I use Kadane’s algorithm because I do not need to keep every possible subarray. At each index, I only need two values. currentSum is the best sum of a contiguous subarray that ends at the current index. maxSum is the best subarray sum seen anywhere so far. The key decision is simple: either start a new subarray at nums[i], or extend the previous subarray by adding nums[i].

3. Initialize the state

At index 0, nums[0] is -2. I set currentSum = -2 and maxSum = -2. Initializing with the first array value is important. If I started both values at 0, an all-negative array could incorrectly return 0 even though 0 is not the maximum sum of any non-empty contiguous subarray.

4. Walk through the example

At i = 1, nums[i] = 1. Starting new gives 1. Extending gives -2 + 1 = -1. I choose 1, so currentSum becomes 1 and maxSum becomes 1.

At i = 2, nums[i] = -3. Starting new gives -3. Extending gives 1 + -3 = -2. I choose -2, so currentSum becomes -2 and maxSum stays 1.

At i = 3, nums[i] = 4. Starting new gives 4. Extending gives -2 + 4 = 2. Starting again is better, so currentSum becomes 4 and maxSum becomes 4.

At i = 4, nums[i] = -1. Starting new gives -1. Extending gives 4 + -1 = 3. I extend, so currentSum becomes 3 and maxSum stays 4.

At i = 5, nums[i] = 2. Starting new gives 2. Extending gives 3 + 2 = 5. I extend, so currentSum becomes 5 and maxSum becomes 5.

At i = 6, nums[i] = 1. Starting new gives 1. Extending gives 5 + 1 = 6. I extend, so currentSum becomes 6 and maxSum becomes 6. This builds the best subarray [4, -1, 2, 1].

At i = 7, nums[i] = -5. Starting new gives -5. Extending gives 6 + -5 = 1. I extend, so currentSum becomes 1 and maxSum stays 6.

At i = 8, nums[i] = 4. Starting new gives 4. Extending gives 1 + 4 = 5. I extend, so currentSum becomes 5 and maxSum stays 6. After all elements are processed, I return 6.

5. Explain why the result is correct

The important invariant is that currentSum always stores the maximum sum of any contiguous subarray ending at the current index. A best subarray ending at i has only two possibilities: it starts at i, or it extends the best subarray ending at i - 1. Taking the larger of nums[i] and currentSum + nums[i] therefore keeps currentSum correct. maxSum records the largest currentSum seen during the traversal, so after the final element it is the maximum subarray sum over the whole array.

6. Explain the Java implementation

The Java solution keeps the same class and maxSubArray method shown in the diagram. It initializes currentSum and maxSum with nums[0]. The loop starts at index 1. For each element, Math.max(nums[i], currentSum + nums[i]) decides whether to start fresh or extend the previous subarray. Then Math.max(maxSum, currentSum) updates the best overall result. A small main method runs the exact diagram example and prints 6.

7. Explain complexity and edge cases

The loop processes each array position once, so the time complexity is O(n). The algorithm only keeps a few integer variables, so the auxiliary space complexity is O(1). Important edge cases are an array with one element, an array containing only negative values, zeros mixed with negative values, and an array containing only positive values. For an all-positive array, the whole array is optimal. For an all-negative array, the largest single value is returned.

Key Insight / Why This Solution Works

The key insight is that when I reach index i, I do not need to remember every earlier subarray. I only need the best contiguous sum that ended at i - 1. For nums[i], there are exactly two useful choices: start a new subarray with nums[i], or extend the previous best ending sum with currentSum + nums[i]. I keep the larger result as the new currentSum. The central invariant is that currentSum is always the best contiguous subarray sum ending at the current index. maxSum keeps the largest currentSum seen so far. This avoids checking many different subarrays separately.

Code
class Solution {

    public int maxSubArray(int[] nums) {
        // Start with the first value so all-negative arrays are handled correctly.
        int currentSum = nums[0];
        int maxSum = nums[0];

        // Traverse from index 1 because index 0 is already used for initialization.
        for (int i = 1; i < nums.length; i++) {
            // Choose whether to start a new subarray at i or extend the previous one.
            currentSum = Math.max(nums[i], currentSum + nums[i]);

            // Record the best contiguous subarray sum seen anywhere so far.
            maxSum = Math.max(maxSum, currentSum);
        }

        // After every element is processed, maxSum is the required answer.
        return maxSum;
    }
}

public class Main {

    public static void main(String[] args) {
        // Use the exact example shown in the approved diagram.
        int[] nums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };

        // Run the diagram's Solution implementation and print the expected result, 6.
        Solution solution = new Solution();
        int result = solution.maxSubArray(nums);
        System.out.println(result);
    }
}
Time & Space Complexity

The time complexity is O(n), where n is the number of elements in nums. We move through the array once from left to right and do constant work at each position. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. We only keep currentSum, maxSum, and the loop index, so the extra memory does not grow as the input becomes larger.

Where it is used

Kadane’s algorithm is useful when software needs the best continuous range according to a running total. For example, it can find the strongest continuous gain in a sequence of gains and losses, or the highest-scoring continuous segment in ordered numeric data. The pattern fits problems where the result must come from one contiguous part of an array.

Why Interviewers Ask This

This problem checks whether you can recognize a running-state pattern instead of trying every possible subarray. The interviewer can see whether you understand the meaning of currentSum, maintain a correct invariant, handle negative values correctly, and translate the reasoning into clean Java. It also tests whether you can explain why restarting a subarray is safe, justify O(n) time and O(1) auxiliary space, and discuss relevant edge cases without changing the problem.

Common interview mistakes

A common mistake is initializing currentSum and maxSum to 0. That fails for an all-negative input such as [-5, -1, -8], where the correct result is -1. Another mistake is updating the running sum but forgetting to keep maxSum for the best result seen earlier. Some candidates incorrectly choose non-contiguous elements, which solves a different problem. Another mistake is always extending the previous sum instead of comparing it with nums[i], so a harmful negative prefix is never discarded. Candidates may also claim O(n) extra space even though this implementation uses only O(1) auxiliary space.

Interview tip

State the invariant before writing the loop: currentSum is the best contiguous sum ending at the current index, and maxSum is the best sum seen anywhere. Then explain each Math.max line using that invariant.

Interviewer may ask next
How would you change the solution if I also wanted the actual maximum-sum subarray, not only its sum?

I would keep the same Kadane’s algorithm and add index tracking. When nums[i] is larger than currentSum + nums[i], I would start a new subarray and set a temporary start index to i. Whenever currentSum becomes larger than maxSum, I would save that temporary start index and the current index as the best range. The invariant is unchanged because currentSum still represents the best subarray ending at i. The time complexity remains O(n), the auxiliary space remains O(1), and the tradeoff is a few extra integer variables.

What happens if every number in the array is negative?

The same algorithm still works because currentSum and maxSum start with nums[0], not 0. At each later index, currentSum compares the current negative value with extending the previous negative sum. This allows the algorithm to restart at a less negative value when that is better. For example, [-5, -1, -8] returns -1. The time complexity remains O(n), and the auxiliary space remains O(1).

3. How would you solve Median of Two Sorted Arrays?CodingHardApple

Question Details

Find the median of two sorted arrays and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would binary-search the shorter array and choose a partition in it. That partition determines the matching partition in the other array, so the left side contains exactly (m + n + 1) / 2 elements. I check the two cross-boundary conditions. If one fails, I move the binary-search range left or right. When both hold, the boundary values give the median. This takes O(log(min(m, n))) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We have two arrays that are already sorted. We need the middle value if we imagine both arrays as one sorted list, but we do not want to build that full list. The key idea is to split both arrays so the left side contains the required number of values. We binary-search the shorter array to find the correct split. Once every value on the left is less than or equal to every value on the right, the middle boundary values give the answer.

Useful Questions to Ask the Interviewer
  1. Can one of the two sorted arrays be empty?
  2. Can I assume the combined input contains at least one value?
How would you solve Median of Two Sorted Arrays? diagram
How to Explain It in an Interview
1. Choose the shorter array for binary search

Let A be the shorter array and B be the longer array. Let m and n be their lengths. We search only partition positions in A. This keeps the search range as small as possible.

For the diagram example, A = [1, 2] and B = [3, 4]. We have m = 2 and n = 2. The required left-side size is half = (m + n + 1) / 2 = 2.

2. Initialize the search interval

Set low = 0 and high = m. These bounds are inclusive because a valid partition can be before the first element or after the last element of A.

A partition position i in A determines the partition j in B with j = half - i. This keeps the total number of values on the left side fixed at half.

3. Check the first partition

Initially, low = 0 and high = 2. The midpoint is i = 1, so j = 1.

The boundary values are Aleft = 1, Aright = 2, Bleft = 3, and Bright = 4. A valid partition needs Aleft <= Bright and Bleft <= Aright.

The first condition is true, but Bleft <= Aright is false because 3 > 2. This means A's partition is too far left. We move right by setting low = i + 1 = 2.

4. Check the second partition and stop

Now low = 2 and high = 2. The midpoint is i = 2, so j = 0.

The boundaries are Aleft = 2, Aright = +infinity, Bleft = -infinity, and Bright = 3. Both cross-boundary checks are now true. The partition is valid, so the search stops.

The combined length is even. The two middle values are max(Aleft, Bleft) = 2 and min(Aright, Bright) = 3. The median is (2 + 3) / 2 = 2.5.

5. Explain why the result is correct

The two left partitions always contain exactly half = (m + n + 1) / 2 elements. Because each input array is sorted, the only possible ordering violations are across the two partition boundaries. When Aleft <= Bright and Bleft <= Aright, every value on the left belongs before every value on the right. Therefore, the median is determined by the partition boundary values.

6. Explain the Java implementation

The Java code first swaps the array references when needed so A is always the shorter array. It uses Integer.MIN_VALUE and Integer.MAX_VALUE as sentinels when a partition touches an array boundary. Each failed partition removes part of the remaining binary-search interval. When a valid partition is found, the code returns either the largest left boundary for an odd total length or the average of the two middle boundaries for an even total length.

7. Explain complexity and edge cases

The binary search is performed only on the shorter array, so the time complexity is O(log(min(m, n))). The algorithm uses only a fixed number of variables, so auxiliary space is O(1). Important cases shown by the diagram include one array being empty, duplicate values, an odd combined length, and a partition at the beginning or end of an array.

Key Insight / Why This Solution Works

The key insight is that we do not need to merge the arrays. We only need the point where the combined sorted order is divided into a left part and a right part. Binary-search a partition i in the shorter array. Compute the matching partition j = half - i in the other array, where half = (m + n + 1) / 2. The invariant is that the two left partitions always contain exactly half elements. If Aleft > Bright, move the partition in A left. If Bleft > Aright, move it right. When both cross-boundary comparisons hold, the partition is correct and the median comes directly from the boundary values.

Code
public class Main {

    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // Always binary-search the shorter array so the search range is minimal.
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int[] a = nums1;
        int[] b = nums2;
        int m = a.length;
        int n = b.length;

        // A valid partition in the shorter array can be from 0 through m.
        int low = 0;
        int high = m;

        // The two left partitions together must contain this many elements.
        int half = (m + n + 1) / 2;

        while (low <= high) {
            // Pick the middle partition in A and derive the paired partition in B.
            int i = low + (high - low) / 2;
            int j = half - i;

            // Use sentinels when a partition touches an array boundary.
            int aLeft = i == 0 ? Integer.MIN_VALUE : a[i - 1];
            int aRight = i == m ? Integer.MAX_VALUE : a[i];
            int bLeft = j == 0 ? Integer.MIN_VALUE : b[j - 1];
            int bRight = j == n ? Integer.MAX_VALUE : b[j];

            // A valid partition has no cross-boundary ordering violation.
            if (aLeft <= bRight && bLeft <= aRight) {
                // For an odd total length, the largest left boundary is the median.
                if (((m + n) & 1) == 1) {
                    return Math.max(aLeft, bLeft);
                }

                // For an even total length, average the two middle boundary values.
                // Cast before addition so the sum is evaluated as a double.
                return ((double) Math.max(aLeft, bLeft) + Math.min(aRight, bRight)) / 2.0;
            } else if (aLeft > bRight) {
                // A's partition is too far right, so move the search interval left.
                high = i - 1;
            } else {
                // B's left boundary is too large, so move A's partition right.
                low = i + 1;
            }
        }

        // Defensive fallback; the stated problem guarantees a solution.
        throw new IllegalArgumentException("No median can be computed.");
    }

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

        // Expected output: 2.5
        System.out.println(findMedianSortedArrays(nums1, nums2));
    }
}
Time & Space Complexity

Let m and n be the two array lengths. We binary-search only the shorter array, so the time complexity is O(log(min(m, n))). Each iteration reads a constant number of array values and performs a constant number of comparisons. The algorithm does not create a merged array or another growing data structure. It uses only variables for the search bounds, partition indices, and boundary values. Therefore, the auxiliary space complexity is O(1).

Where it is used

This partition-based binary-search pattern is useful when two sequences are already sorted and we need an order statistic, such as the median, without physically merging all values. It is especially useful when random access is available and creating a combined array would do unnecessary work or use extra memory.

Why Interviewers Ask This

This problem tests whether you can recognize that merging the arrays is unnecessary. The interviewer wants to see strong binary-search reasoning, especially how you search for a valid partition instead of a particular value. It also tests whether you can maintain an invariant, handle array boundaries safely, move the search interval in the correct direction, calculate the median for odd and even lengths, write correct Java, and explain why the complexity is O(log(min(m, n))) with O(1) auxiliary space.

Common interview mistakes

A common mistake is binary-searching the longer array, which loses the intended O(log(min(m, n))) bound. Another is computing j independently instead of using j = half - i, which breaks the fixed left-side size. Candidates also move the search range in the wrong direction when a cross-boundary check fails. Boundary partitions must use safe sentinel values or equivalent checks. For an even combined length, the median must use max(Aleft, Bleft) and min(Aright, Bright), not values from only one array. The average should be evaluated as a floating-point calculation rather than integer division.

Interview tip

State the partition invariant before coding: the two left partitions together always contain half = (m + n + 1) / 2 elements. Then explain that the two cross-boundary comparisons tell you whether to move left, move right, or return the median.

Interviewer may ask next
Why do you binary-search the shorter array instead of either array?

Searching the shorter array guarantees that the binary-search interval has at most min(m, n) + 1 partition positions, so the time complexity is O(log(min(m, n))). It also keeps the derived partition j within the longer array's valid partition range when i is within the shorter array's valid range. The partition invariant and median calculation do not change. Auxiliary space remains O(1).

Does this approach still work when the arrays contain duplicate values?

Yes. The partition checks use <= instead of strict <. Equal values can therefore appear on either side of the partition without breaking sorted order. The same invariant still holds: the two left partitions contain the required number of elements, and the cross-boundary values are ordered correctly. The algorithm does not change. Time remains O(log(min(m, n))) and auxiliary space remains O(1).

4. How would you solve Number of Islands?CodingMediumApple

Question Details

Count connected components of land in a grid and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would scan the grid from top-left to bottom-right. When I find a land cell, '1', I count a new island and start DFS from that cell. DFS marks every horizontally or vertically connected land cell as '0', so the same island cannot be counted again. I continue until the full grid is scanned. The total time is O(m × n), and the recursive DFS can use O(m × n) auxiliary space in the worst case.

Detailed Explanation

See the Code while reading this explanation.

We have a grid containing land marked '1' and water marked '0'. We need to count how many separate groups of land exist. Land belongs to the same group only when cells touch up, down, left, or right. Diagonal touching does not join groups. I scan the grid in order. When I find land that has not already been handled, I count one new group and visit all connected land from that cell. I mark those cells so they cannot be counted again.

Useful Questions to Ask the Interviewer
  1. Can I modify the input grid while processing it?
  2. Should diagonal land cells be treated as disconnected? In this problem, only up, down, left, and right connections count.
How would you solve Number of Islands? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an m × n grid of characters. A '1' means land and a '0' means water. The output is one integer: the number of separate islands. Two land cells are connected only through up, down, left, or right moves. Diagonal contact does not connect them.

The diagram uses this exact 4 × 5 example: 1 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 1

The expected result is 3.

2. Choose DFS with in-place marking

I use depth-first search, or DFS. DFS starts from one land cell and visits its whole connected component. A connected component means all land cells reachable from each other using the four allowed directions.

Instead of keeping a separate visited array, the solution changes each visited '1' to '0'. The central invariant is simple: once a land cell has been visited and changed to '0', it cannot cause the same island to be counted again.

3. Initialize and scan the grid

If the grid is null or empty, the method returns 0. Otherwise, it stores the row count and column count and sets islands to 0.

The two loops scan cells row by row from top-left to bottom-right. Whenever grid[r][c] is '1', that cell is the first unvisited cell of a new island. The algorithm increments islands and starts DFS from that position.

4. Walk through the exact example

The scan first reaches (0,0), which contains '1'. islands changes from 0 to 1. DFS marks (0,0), (0,1), (1,0), and (1,1) as '0'. This removes the complete 2 × 2 island from future counting.

The scan continues until (2,2), which still contains '1'. islands changes from 1 to 2. DFS marks (2,2). Its four neighbors do not contain connected land, so this island contains one cell.

The scan later reaches (3,3), which contains '1'. islands changes from 2 to 3. DFS marks (3,3) and (3,4). This removes the final two-cell island.

The scan then completes. The method returns 3. The three components are {(0,0),(0,1),(1,0),(1,1)}, {(2,2)}, and {(3,3),(3,4)}.

5. Explain why the result is correct

Every island has a first land cell reached by the row-by-row scan. When that first unvisited '1' is found, the count increases exactly once. DFS then changes every four-directionally connected land cell in that island to '0'. Because those cells are no longer '1', no later scan position can count the same island again. Therefore, every island is counted exactly once.

6. Explain the Java implementation

numIslands handles the empty-input case first. It then scans every grid position. When the current cell is '1', it increments islands and calls dfs.

The DFS base case returns when the coordinates are outside the grid or the current cell is already '0'. For valid land, DFS immediately changes the cell to '0'. It then recursively explores down, up, right, and left. Marking the cell before recursion prevents repeated visits. After the outer scan finishes, numIslands returns islands.

7. Explain complexity and edge cases

The time complexity is O(m × n), where m is the number of rows and n is the number of columns. The outer scan examines the grid, and each land cell is marked visited once. The DFS work across the whole grid remains O(m × n).

The worst-case recursion stack is O(m × n), such as when one large island contains most or all cells. Relevant edge cases are an empty grid, an all-water grid, one large island, diagonal land that must stay disconnected, and scattered single-cell islands. If the input grid cannot be modified, a boolean[][] visited array can be used instead.

Key Insight / Why This Solution Works

The key idea is to count connected components. Scan the grid from top-left to bottom-right. Each time a remaining '1' is found, it must belong to an island that has not been counted yet. Increment the island count and run DFS from that cell. DFS visits the entire four-directionally connected component and changes every visited land cell to '0'. The central invariant is that once a land cell is marked '0', that cell cannot start another island later. This makes each connected component contribute exactly one to the final count.

Code
public class Main {

    public static void main(String[] args) {
        // Use the exact 4 x 5 example from the diagram.
        char[][] grid = {
            { '1', '1', '0', '0', '0' },
            { '1', '1', '0', '0', '0' },
            { '0', '0', '1', '0', '0' },
            { '0', '0', '0', '1', '1' },
        };

        // Run the same Solution implementation shown in the diagram.
        Solution solution = new Solution();
        int result = solution.numIslands(grid);

        // The expected result for this example is 3.
        System.out.println(result);
    }

    static class Solution {

        public int numIslands(char[][] grid) {
            // A null or empty grid contains no islands.
            if (grid == null || grid.length == 0) {
                return 0;
            }

            // Store the dimensions used by the row-by-row scan.
            int rows = grid.length;
            int cols = grid[0].length;
            int islands = 0;

            // Scan every cell from top-left to bottom-right.
            for (int r = 0; r < rows; r++) {
                for (int c = 0; c < cols; c++) {
                    // A remaining '1' is the first unseen cell of a new island.
                    if (grid[r][c] == '1') {
                        islands++;

                        // Mark this complete connected component as visited.
                        dfs(grid, r, c);
                    }
                }
            }

            // Each discovered connected component has been counted once.
            return islands;
        }

        private void dfs(char[][] grid, int r, int c) {
            int rows = grid.length;
            int cols = grid[0].length;

            // Stop outside the grid or when this cell is water/already visited.
            if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] == '0') {
                return;
            }

            // Mark the cell before recursion so it cannot be visited again.
            grid[r][c] = '0';

            // Explore exactly the four directions used in the diagram.
            dfs(grid, r + 1, c); // down
            dfs(grid, r - 1, c); // up
            dfs(grid, r, c + 1); // right
            dfs(grid, r, c - 1); // left
        }
    }
}
Time & Space Complexity

Let m be the number of rows and n be the number of columns. The time complexity is O(m × n). The grid is scanned once, and DFS marks each land cell as visited so it does not need to be explored again as land. The worst-case auxiliary space is O(m × n) because recursive DFS calls can become that deep when one very large island fills the grid. The solution does not need a separate visited matrix because it stores the visited state directly in the input grid by changing '1' to '0'.

Where it is used

This connected-component pattern is useful when software must find separate regions in grid-shaped data. Examples include identifying connected areas in image masks, grouping neighboring map cells, finding regions in board games, and exploring reachable areas in matrix-based simulations. The same DFS idea is also used to count separate connected groups in general graphs.

Why Interviewers Ask This

This problem checks whether you can recognize connected components in a grid and apply DFS correctly. The interviewer can evaluate whether you handle boundaries, mark visited cells at the correct time, respect four-directional connectivity, and keep the scan and recursion consistent. It also tests whether you can explain why each island is counted exactly once, write clear Java recursion, and include the recursion stack when discussing space complexity.

Common interview mistakes

A common mistake is treating diagonal land cells as connected even though only up, down, left, and right connections count. Another mistake is marking a land cell after recursive calls instead of before them, which can cause repeated visits. Candidates may also forget the out-of-bounds DFS base case. Another error is saying the solution uses O(1) auxiliary space even though recursive DFS can use O(m × n) stack space. Finally, if the interviewer does not allow input mutation, changing '1' to '0' is not acceptable and a separate visited structure is needed.

Interview tip

Explain the invariant before writing the DFS: every remaining '1' found by the scan starts exactly one new island, and DFS immediately changes that whole four-directionally connected component to '0'. That makes the counting logic and correctness easy to justify.

Interviewer may ask next
What would you change if modifying the input grid is not allowed?

I would keep the same row-by-row scan and DFS, but add a boolean[][] visited array with the same dimensions as the grid. A new island starts when grid[r][c] is '1' and visited[r][c] is false. DFS would mark visited[r][c] = true instead of changing the grid cell to '0'. Correctness stays the same because every visited land cell is prevented from starting another island. Time remains O(m × n). The visited array uses O(m × n) extra space, and the recursive stack can also use O(m × n) in the worst case.

How would you handle a very large grid where recursive DFS could overflow the Java call stack?

I would replace recursive DFS with an explicit stack while keeping the same connected-component approach. When the scan finds a fresh '1', I count one island, mark that cell visited, push it onto the stack, and repeatedly pop cells and process their valid four-directional land neighbors. Each cell is still processed at most once as land, so time remains O(m × n). The explicit stack can use O(m × n) space in the worst case. The tradeoff is slightly more code, but it avoids deep recursion.

5. How would you solve Best Time to Buy and Sell Stock?CodingEasyApple

Question Details

Solve the single-transaction stock profit problem and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would keep the lowest stock price seen so far and the best profit found so far. I start with the first price as minPrice, then move from left to right. For each later price, I calculate the profit if I sell today. I update maxProfit when that profit is better, and I update minPrice when I find a cheaper buy price. This works in O(n) time and uses O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We are given stock prices for different days. We may buy the stock once and sell it once on a later day. We want the largest possible profit. If no positive profit is possible, the answer is 0. The main idea is to remember the cheapest earlier price while moving from left to right. Then each current day can be tested as a possible selling day. This avoids checking every possible pair of days and gives a simple one-pass solution.

Useful Questions to Ask the Interviewer
  1. Should I return only the maximum profit, not the buy and sell indices?
  2. If no profitable transaction exists, should I return 0?
  3. Can the input be empty or contain only one price?
How would you solve Best Time to Buy and Sell Stock? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an integer array called prices. prices[i] is the stock price on day i. We may buy on one day and sell on a later day. We return the maximum profit from one transaction. We do not return the buy and sell indices. If no positive profit is possible, we return 0.

For the example prices = [7, 1, 5, 3, 6, 4], the answer is

  1. We buy at index 1 for price 1 and sell at index 4 for price
  2. The profit is 6 - 1 = 5.
2. Choose the algorithm and maintain the invariant

I use a single left-to-right pass. I keep two integer variables. minPrice stores the lowest price seen on an earlier processed day. maxProfit stores the best valid buy-then-sell profit found so far.

The important invariant is this: before processing index i, minPrice is the lowest price seen on earlier processed days, and maxProfit is the best valid profit found so far.

3. Initialize the state

If prices is null or has fewer than two elements, there is no possible buy followed by a later sell, so the method returns 0.

Otherwise, minPrice starts as prices[0], which is 7 in the example. maxProfit starts as 0. Traversal begins at index 1 because index 0 has already been used to initialize minPrice.

4. Walk through the example

At index 1, the price is 1. Before this step, minPrice = 7 and maxProfit = 0. We calculate currentProfit = 1 - 7 = -6. Since -6 is not greater than 0, maxProfit stays 0. Because 1 is less than 7, minPrice becomes 1. After the step, minPrice = 1 and maxProfit = 0.

At index 2, the price is 5. Before the step, minPrice = 1 and maxProfit = 0. We calculate currentProfit = 5 - 1 = 4. Since 4 is greater than 0, maxProfit becomes 4. minPrice stays 1. After the step, minPrice = 1 and maxProfit = 4.

At index 3, the price is 3. Before the step, minPrice = 1 and maxProfit = 4. We calculate currentProfit = 3 - 1 = 2. Since 2 is not greater than 4, maxProfit stays 4. The price 3 is not less than minPrice, so minPrice stays 1. After the step, minPrice = 1 and maxProfit = 4.

At index 4, the price is 6. Before the step, minPrice = 1 and maxProfit = 4. We calculate currentProfit = 6 - 1 = 5. Since 5 is greater than 4, maxProfit becomes 5. minPrice stays 1. After the step, minPrice = 1 and maxProfit = 5.

At index 5, the price is 4. Before the step, minPrice = 1 and maxProfit = 5. We calculate currentProfit = 4 - 1 = 3. Since 3 is not greater than 5, maxProfit stays 5. The price 4 is not less than minPrice, so minPrice stays 1. After the step, minPrice = 1 and maxProfit = 5.

The loop is now complete, so the method returns 5.

5. Explain why the result is correct

When we calculate the profit for a selling day, minPrice represents the cheapest price from an earlier day. Therefore, every profit we compare represents a valid buy first and sell later transaction. We treat each later day as a possible selling day and keep the largest profit. When the loop finishes, maxProfit is therefore the best single-transaction profit.

6. Explain the Java implementation

The Java method first handles null input and arrays with fewer than two prices. It initializes minPrice with the first price and maxProfit with

  1. The for loop starts at index
  2. For each current price, it first calculates currentProfit using the existing minPrice. It updates maxProfit if that profit is better. It then updates minPrice if the current price is cheaper, so that lower price can be used by future selling days. Finally, it returns maxProfit.
7. Explain complexity and edge cases

The time complexity is O(n) because the algorithm examines each price at most once. The auxiliary space complexity is O(1) because it uses only a few integer variables. Important edge cases are an empty array, one element, strictly decreasing prices, duplicate prices, and a price of 0.

Key Insight / Why This Solution Works

The key insight is that when we consider a day as a possible selling day, we only need the cheapest price that appeared before it. The algorithm stores that value in minPrice. It moves from left to right and calculates currentProfit = prices[i] - minPrice. If currentProfit is larger than maxProfit, it saves the new best profit. After checking the profit, it updates minPrice if the current price is cheaper. The central invariant is that before processing index i, minPrice is the cheapest earlier price and maxProfit is the best valid profit found so far. This avoids the O(n^2) approach of checking every pair of days.

Code
public class Main {

    public static int maxProfit(int[] prices) {
        // A valid transaction needs a buy day and a later sell day.
        // Return 0 when the input has fewer than two prices.
        if (prices == null || prices.length < 2) {
            return 0;
        }

        // The first price is the lowest price seen before index 1.
        int minPrice = prices[0];

        // Start at 0 so an unprofitable trade never creates a negative answer.
        int maxProfit = 0;

        // Index 0 already initialized minPrice, so begin with index 1.
        for (int i = 1; i < prices.length; i++) {
            // Treat the current day as a possible sell day.
            // minPrice still represents the cheapest price from an earlier day.
            int currentProfit = prices[i] - minPrice;

            // Save the best valid single-transaction profit found so far.
            if (currentProfit > maxProfit) {
                maxProfit = currentProfit;
            }

            // After evaluating today's sell possibility, save a cheaper price
            // so it can be used as the buy price for future selling days.
            if (prices[i] < minPrice) {
                minPrice = prices[i];
            }
        }

        // Return the best profit, or 0 if no positive profit was possible.
        return maxProfit;
    }

    public static void main(String[] args) {
        // Exact example from the approved diagram.
        int[] prices = { 7, 1, 5, 3, 6, 4 };

        // Expected output: 5.
        System.out.println(maxProfit(prices));
    }
}
Time & Space Complexity

The time complexity is O(n). We move through the array from left to right and examine each price at most once. For n prices, the work grows directly with n. The auxiliary space complexity is O(1). Auxiliary space means extra memory used by the algorithm. We only store minPrice, maxProfit, currentProfit, and the loop index, so the amount of extra memory does not grow with the number of prices.

Where it is used

This running-minimum pattern is useful when we need the best difference between a later value and the smallest earlier value. It can appear in price analysis, profit calculations, measurements over time, and other ordered data where one action must happen before a later action.

Why Interviewers Ask This

This problem tests whether a candidate can replace an O(n^2) pair-checking approach with a simple O(n) scan. It also checks whether the candidate understands ordering because the buy must occur before the sell. The interviewer can evaluate how well the candidate maintains an invariant, updates state in the correct order, handles cases such as decreasing or duplicate prices, writes clear Java code, and explains O(n) time with O(1) auxiliary space.

Common interview mistakes

A common mistake is solving the multiple-transactions version instead of allowing only one buy and one later sell. Another mistake is checking every pair of days with two nested loops, which takes O(n^2) time. Candidates can also mix up values and indices when explaining the example: index 1 has price 1, and index 4 has price 6. Another mistake is returning a negative profit for decreasing prices instead of 0. Finally, candidates may calculate the profit from the wrong state instead of using the cheapest earlier price.

Interview tip

State the invariant before writing the loop: before each index, minPrice is the cheapest earlier buy price and maxProfit is the best profit found so far. Then trace [7, 1, 5, 3, 6, 4] to show that the final profit becomes 5.

Interviewer may ask next
How would the solution change if multiple buy and sell transactions were allowed?

The problem would change because we would no longer keep only the best single transaction. We could add every positive increase between consecutive days. Whenever prices[i] is greater than prices[i - 1], we add prices[i] - prices[i - 1] to the total profit. This captures all upward price movement while keeping each sell after its buy. The time complexity is O(n), and the auxiliary space complexity is O(1). The tradeoff is that this solves the multiple-transactions version, not the original one-transaction problem.

How would you return the actual buy and sell indices instead of only the profit?

I would keep the same left-to-right O(n) scan. Along with minPrice, I would store minIndex, which is the index of that minimum price. Whenever currentProfit becomes larger than maxProfit, I would save minIndex as the best buy index and the current index as the best sell index. When a new lower price is found, I would update both minPrice and minIndex. The invariant stays the same, and the returned indices always keep the buy before the sell. Time remains O(n), and auxiliary space remains O(1).

6. How would you solve Longest Common Prefix?CodingEasyApple

Question Details

Find the longest common prefix and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would use the first string as the reference and compare one character position at a time across all the other strings. I move from left to right. If another string ends at the current position or has a different character, I return the part of the reference before that index. Otherwise, I continue. This works because every earlier checked position matches in all strings. The time complexity is O(S), where S is the characters compared, and auxiliary space is O(1).

Detailed Explanation

See the Code while reading this explanation.

We are given an array of strings and need to find the longest text that appears at the very beginning of every string. The matching characters must start at the first character and stay continuous. I use the first string as a reference and compare one position at a time with every other string. As soon as one string ends or has a different character, the shared beginning must stop there. This method directly follows the definition of a common prefix, can stop early, and does not need an extra data structure.

Useful Questions to Ask the Interviewer
  1. Can the input array be null or empty?
  2. Can one of the strings be empty?
  3. Should character comparison use the strings exactly as provided, including letter case?
How would you solve Longest Common Prefix? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an array of strings. We must return the longest prefix shared by every string. A prefix starts at index 0 and contains continuous characters. For the diagram example, the input is ["flower", "flow", "flight"], and the expected output is "fl".

2. Choose the algorithm

I use vertical scanning. The first string, "flower", is the reference. I compare its character at index i with the character at the same index in every remaining string. No extra collection is needed.

The central invariant is: before checking position i, every position before i already matches in all strings.

3. Initialize the state

The reference is "flower". There are three strings. Processing starts at i = 0. For each reference position, the inner loop checks "flow" and then "flight" at that same position.

4. Walk through the example

At i = 0, currentChar is 'f'. flow[0] is 'f' and flight[0] is 'f'. All strings match, so processing continues.

At i = 1, currentChar is 'l'. flow[1] is 'l' and flight[1] is 'l'. All strings still match, so the common prefix so far is "fl".

At i = 2, currentChar is 'o'. flow[2] is 'o', but flight[2] is 'i'. The condition detects a mismatch in "flight". Processing stops immediately. The method returns reference.substring(0, 2), which is "fl". Indices after 2 are not processed.

5. Explain why the result is correct

Before index i is checked, reference[0..i-1] is already known to match every string. Therefore, the first mismatch or the first point where another string ends is exactly where the common prefix must stop. Returning the substring before that position gives the longest possible common prefix.

6. Explain the Java implementation

The method first handles a null or empty input array by returning "". It stores strs[0] as reference. The outer loop visits reference from left to right. The inner loop checks the same index in every other string. It first checks whether that string ends at the current position. It also checks whether its character differs from currentChar. Either condition causes an immediate return. If every character in reference matches, the method returns the entire reference.

7. Explain complexity and edge cases

The time complexity is O(S), where S is the total number of characters compared across the strings. The algorithm can stop before examining all available characters. Auxiliary space is O(1), excluding the returned substring. The relevant edge cases are an empty array, one input string, no common prefix, and a shorter string that limits the possible prefix.

Key Insight / Why This Solution Works

Use vertical scanning with the first string as the reference. For each index i in the reference, read currentChar and compare it with the character at index i in every remaining string. If another string has length i, that string has ended and the prefix cannot continue. If its character is different, the prefix also cannot continue. In either case, return reference.substring(0, i). The invariant is that before index i is checked, reference[0..i-1] matches every string. Because of that invariant, the first failure position is exactly the boundary of the longest common prefix.

Code
public class Main {

    public static void main(String[] args) {
        // Use the exact example shown in the approved diagram.
        String[] strs = { "flower", "flow", "flight" };

        // Run the same Solution method shown in the diagram.
        Solution solution = new Solution();
        String result = solution.longestCommonPrefix(strs);

        // The expected printed result is: fl
        System.out.println(result);
    }

    static class Solution {

        public String longestCommonPrefix(String[] strs) {
            // Handle a null or empty input array before accessing strs[0].
            if (strs == null || strs.length == 0) {
                return "";
            }

            // Use the first string as the reference for all position checks.
            String reference = strs[0];

            // Move through the reference string from left to right.
            for (int i = 0; i < reference.length(); i++) {
                // This is the character every other string must match at index i.
                char currentChar = reference.charAt(i);

                // Compare the same character position in every remaining string.
                for (int j = 1; j < strs.length; j++) {
                    // Stop if this string ends here or has a different character.
                    // Everything before i is the longest common prefix.
                    if (i == strs[j].length() || strs[j].charAt(i) != currentChar) {
                        return reference.substring(0, i);
                    }
                }
            }

            // If every reference position matched, the whole reference is common.
            return reference;
        }
    }
}
Time & Space Complexity

Time is O(S), where S is the total number of characters compared across the strings. The algorithm compares characters only until it finds a mismatch or reaches the end of a shorter string, so it can stop early. In the diagram example, it processes indices 0, 1, and 2 and stops at the mismatch at index 2. Auxiliary space is O(1) because only the reference, loop indices, and current character are stored. This excludes memory used by the returned substring.

Where it is used

This character-by-character comparison pattern is useful when software needs to find a shared beginning among strings. Examples include comparing path prefixes, grouping names or identifiers by a common start, and checking common beginnings in search or command strings. It is especially useful when a direct comparison is enough and no additional data structure is needed.

Why Interviewers Ask This

This problem tests whether you can convert a simple string requirement into safe index-based code. The interviewer can evaluate how you handle shorter strings, empty input, character comparison, nested loops, and early return. It also tests whether you can maintain and explain a useful invariant. In Java, the problem checks correct use of length(), charAt(), and substring(). Finally, it shows whether you can give an accurate O(S) time bound and O(1) auxiliary-space explanation.

Common interview mistakes

One mistake is reading strs[j].charAt(i) before checking whether that string has already ended. That can cause an index error. Another mistake is continuing after the first mismatch instead of returning immediately. Candidates may also compare different positions between strings, which does not test a common prefix. Another common error is confusing a prefix with a subsequence. A prefix must start at index 0 and remain continuous. Finally, do not claim a simpler O(n) bound without defining n. For this implementation, O(S) describes the total characters compared.

Interview tip

State the invariant before writing the loops: every position before i already matches in all strings. Then the early return is easy to explain because the first mismatch or string end is exactly the prefix boundary.

Interviewer may ask next
What happens if one of the strings is empty?

The same algorithm handles it without changing the main approach. When i is 0, the condition i == strs[j].length() is true for an empty string because both values are 0. The method immediately returns reference.substring(0, 0), which is "". This is correct because no non-empty prefix can be shared with an empty string. The worst-case time remains O(S), auxiliary space remains O(1), and this particular case stops immediately.

Could we use the shortest string as the reference instead of the first string?

Yes. We could first find the shortest string and use it as the reference. Correctness is preserved because a common prefix can never be longer than the shortest input string. The comparison step would still scan character positions and stop at the first mismatch. The overall worst-case time remains O(S), and auxiliary space remains O(1). The tradeoff is an additional pass to identify the shortest string, while the diagram's current solution avoids that extra setup and simply uses strs[0].

7. How would you solve Move Zeroes?CodingEasyApple

Question Details

Move all zeroes to the end while preserving relative order and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would use an in-place two-pointer approach. I keep insertPos at the next position where a non-zero value should go, while current scans the array from left to right. When current finds a non-zero value, I swap it with nums[insertPos] and then move insertPos forward. Zeroes are skipped. This preserves the relative order of the non-zero values and moves all zeroes to the end. The time complexity is O(n), and the auxiliary space complexity is O(1).

Detailed Explanation

See the Code while reading this explanation.

The input is an integer array. The goal is to change that same array so every zero is at the end. The non-zero numbers must stay in the same relative order as before. For example, [0, 1, 0, 3, 12] must become [1, 3, 12, 0, 0]. We can do this without creating another array. We keep one position for where the next non-zero number belongs. Then we read the array from left to right and move each non-zero number into that position.

Useful Questions to Ask the Interviewer
  1. Should I modify the input array in place?
  2. Must the relative order of the non-zero values stay unchanged?
  3. Can the input array be empty?
How would you solve Move Zeroes? diagram
How to Explain It in an Interview
1. Understand the input and required output

We receive an integer array and modify it in place. The result is the same array with every zero moved to the end. The non-zero values must keep their original relative order. For the example [0, 1, 0, 3, 12], the final array is [1, 3, 12, 0, 0].

2. Choose the two-pointer approach

I use two integer positions. current scans every array position from left to right. insertPos tells me where the next non-zero value should be placed. The important invariant is that, before each step, indices from 0 through insertPos - 1 contain exactly the non-zero values seen so far in their original order.

3. Initialize the state

The array starts as [0, 1, 0, 3, 12]. I set insertPos = 0 because no non-zero values have been placed yet. The traversal starts with current = 0.

4. Walk through the example

At current = 0, the value is 0. The condition nums[current] != 0 is false, so I skip it. The array stays [0, 1, 0, 3, 12], and insertPos stays 0.

At current = 1, the value is 1. It is non-zero, so I swap nums[0] and nums[1]. The array changes from [0, 1, 0, 3, 12] to [1, 0, 0, 3, 12]. Then insertPos becomes 1.

At current = 2, the value is 0. I skip it. The array stays [1, 0, 0, 3, 12], and insertPos stays 1.

At current = 3, the value is 3. It is non-zero, so I swap nums[1] and nums[3]. The array changes from [1, 0, 0, 3, 12] to [1, 3, 0, 0, 12]. Then insertPos becomes 2.

At current = 4, the value is 12. It is non-zero, so I swap nums[2] and nums[4]. The array changes from [1, 3, 0, 0, 12] to [1, 3, 12, 0, 0]. Then insertPos becomes 3. current has now reached the end of the array, so the traversal stops.

5. Explain why the result is correct

Each non-zero value is placed into the next open position from the left. Because current moves only from left to right, those non-zero values are placed in the same order in which they originally appear. Zeroes are skipped. As non-zero values are swapped left, zeroes move toward the right. When the traversal finishes, the front contains the non-zero values in their original order and the remaining positions contain zeroes.

6. Explain the Java implementation, complexity, and edge cases

The Java loop visits each index from 0 to nums.length - 1. It performs a swap only when nums[current] is non-zero, then increments insertPos. The method returns nothing because it modifies nums directly. The algorithm takes O(n) time and O(1) auxiliary space. The same loop handles an empty array, a one-element array, an array containing only zeroes, an already compact array with no zeroes, and arrays with zeroes at the front, middle, or end.

Key Insight / Why This Solution Works

The key idea is stable in-place compaction using two positions. insertPos marks the next index where a processed non-zero value belongs. current scans from left to right. If nums[current] is zero, the algorithm skips it. If nums[current] is non-zero, that value is swapped into nums[insertPos], and insertPos advances by one. The central invariant is that indices [0 .. insertPos - 1] always contain exactly the non-zero values processed so far, in their original relative order. This gives the required ordering without using another array.

Code
import java.util.Arrays;

public class Main {

    public static void moveZeroes(int[] nums) {
        // insertPos marks the next index where a non-zero value should be placed.
        int insertPos = 0;

        // Scan every element from left to right.
        // An empty array is handled naturally because this loop will not execute.
        for (int current = 0; current < nums.length; current++) {
            // A zero is skipped. Only non-zero values are compacted toward the left.
            if (nums[current] != 0) {
                // Save the value currently at insertPos before changing that position.
                int temp = nums[insertPos];

                // Move the current non-zero value into the next open position.
                nums[insertPos] = nums[current];

                // Put the saved value at the current position.
                // This performs the same in-place swap shown in the diagram.
                nums[current] = temp;

                // The next non-zero value belongs one position farther to the right.
                insertPos++;
            }
        }
    }

    public static void main(String[] args) {
        // Use the exact example from the approved diagram.
        int[] nums = { 0, 1, 0, 3, 12 };

        // Modify the same array in place.
        moveZeroes(nums);

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

Let n be the number of elements in the array. The time complexity is O(n) because current moves from index 0 to index n - 1 and processes every array position once. Each condition check and swap takes constant time. The auxiliary space complexity is O(1) because the algorithm uses only insertPos, current, and one temporary value for swapping. It does not allocate another array or another data structure whose size grows with n.

Where it is used

This two-pointer compaction pattern is useful when items must be rearranged inside an existing array while keeping the useful items in their original order. Similar logic can compact valid records toward the front, move placeholder values toward the end, or remove unwanted markers in place when extra memory should be avoided.

Why Interviewers Ask This

This problem checks whether you can recognize an in-place two-pointer pattern and maintain a clear invariant while changing an array. The interviewer can evaluate whether you preserve relative order, move the correct pointer at the correct time, handle zeroes without extra storage, and reason about mutation. It also tests whether you can write correct Java code, trace intermediate states accurately, discuss useful edge cases, and explain why the solution takes O(n) time and O(1) auxiliary space.

Common interview mistakes
  1. Using a second array even though this problem can be solved with O(1) auxiliary space.
  2. Reordering the non-zero values instead of preserving their original relative order.
  3. Incrementing insertPos when nums[current] is zero. insertPos should advance only after placing a non-zero value.
  4. Writing only one side of the swap and losing the value that was previously stored at insertPos.
  5. Claiming O(1) auxiliary space while using an extra array whose size grows with the input.
Interview tip

State the invariant before you code: everything before insertPos is already the processed non-zero prefix in the correct relative order. Then trace [0, 1, 0, 3, 12] and show that insertPos moves only after a non-zero value is placed.

Interviewer may ask next
What happens if the array contains only zeroes?

The algorithm works without any change. current visits each element, but nums[current] != 0 is never true. No swaps happen and insertPos remains 0. The array therefore stays unchanged, which is correct because all of its values are already zeroes. The time complexity is O(n), and the auxiliary space complexity remains O(1).

Can we avoid unnecessary swaps when insertPos equals current?

Yes. Before swapping, we could check whether insertPos != current. If the two positions are equal, the non-zero value is already in the correct location, so no swap is needed and we only increment insertPos. The same invariant and relative order are preserved. The time complexity remains O(n), and the auxiliary space remains O(1). The tradeoff is one extra condition in exchange for avoiding self-swaps.

8. How would you solve Valid Parentheses?CodingEasyApple

Question Details

Validate balanced parentheses and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would use a stack to keep the opening brackets that have not been matched yet. I read the string from left to right. I push each opening bracket. For a closing bracket, I first check that the stack is not empty, then pop the top and verify that the bracket types match. This also enforces the correct nesting order. At the end, the stack must be empty. The time complexity is O(n), and the auxiliary space is O(n).

Detailed Explanation

See the Code while reading this explanation.

The input is a string made only of parentheses, square brackets, and curly brackets. We need to return true only when every opening bracket has the correct closing bracket and the brackets close in the correct order. A stack fits this problem because the most recently opened bracket must be closed first. We move from left to right, store opening brackets, and compare each closing bracket with the top of the stack. The example in the diagram is s = "([{}])", which returns true.

Useful Questions to Ask the Interviewer
  1. Should an empty string be considered valid and return true?
  2. Can I assume the input contains only '(', ')', '[', ']', '{', and '}' as stated?
How would you solve Valid Parentheses? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is one string. Every character is one of the six bracket characters. We return a boolean value. We return true only if every opening bracket has a matching closing bracket of the same type and the nesting order is correct.

For the example s = "([{}])", the characters and indices are: 0 -> '(' 1 -> '[' 2 -> '{' 3 -> '}' 4 -> ']' 5 -> ')'

The expected result is true.

2. Choose a stack and define the invariant

I use a stack. Each stack entry is an opening bracket that has been seen but not matched yet. The top of the stack is the most recent unmatched opening bracket.

The central invariant is: the stack always contains exactly the unmatched opening brackets seen so far, in nesting order. Therefore, when a closing bracket appears, it must match the current top.

3. Initialize and process the string

Start with an empty stack: []. Begin at index 0 and move from left to right.

If the current character is '(', '[', or '{', push it onto the stack. If it is a closing bracket, first check whether the stack is empty. An empty stack means there is no opening bracket available to match, so return false. Otherwise, pop the top opening bracket and compare the types. Return false immediately if they do not match.

4. Walk through the example

Step 1: i = 0, ch = '('. The stack is []. This is an opening bracket, so push it. The stack becomes ['(']. Continue.

Step 2: i = 1, ch = '['. The stack is ['(']. Push '['. The stack becomes ['(', '[']. Continue.

Step 3: i = 2, ch = '{'. The stack is ['(', '[']. Push '{'. The stack becomes ['(', '[', '{']. Continue.

Step 4: i = 3, ch = '}'. The stack is ['(', '[', '{']. The top is '{', which matches '}'. Pop it. The stack becomes ['(', '[']. Continue.

Step 5: i = 4, ch = ']'. The stack is ['(', '[']. The top is '[', which matches ']'. Pop it. The stack becomes ['(']. Continue.

Step 6: i = 5, ch = ')'. The stack is ['(']. The top is '(', which matches ')'. Pop it. The stack becomes []. Continue.

All 6 characters have now been processed. The stack is empty, so return true.

5. Explain why the result is correct

Every opening bracket is pushed when it appears. A closing bracket can succeed only when it matches the most recent unmatched opening bracket at the top of the stack. This checks both bracket type and nesting order. If all comparisons succeed and no unmatched opening bracket remains at the end, the whole string is balanced. For "([{}])", every closing bracket matches correctly and the final stack is empty, so the returned value is true.

6. Explain the Java implementation

The Java code uses Deque<Character> with ArrayDeque as the stack. stack.push(ch) adds an opening bracket to the top. Before processing a closing bracket, stack.isEmpty() prevents an invalid pop. stack.pop() removes the opening bracket that must be matched next. Three direct comparisons check (), [], and {}. Any mismatch returns false immediately. After the loop, stack.isEmpty() is returned so unmatched opening brackets also make the result false.

7. Explain complexity and edge cases

Let n be the number of characters. Each character is processed at most once. Each stack push or pop is O(1), so the total time is O(n). The stack can contain up to n opening brackets, so auxiliary space is O(n).

Important edge cases from the diagram are: an empty string returns true, "(" returns false, ")" returns false, "([)]" returns false because the nesting order is wrong, and "()[]{}" returns true.

Key Insight / Why This Solution Works

The key idea is that valid brackets close in the reverse order in which they open. A stack directly represents this rule. Each opening bracket is pushed onto the stack. For every closing bracket, the algorithm checks the most recent unmatched opening bracket at the top. If the stack is empty or the types do not match, the string is invalid immediately. The invariant is that the stack contains exactly the unmatched opening brackets seen so far, with the newest one on top. After processing the whole string, an empty stack means every opening bracket was matched correctly.

Code
import java.util.ArrayDeque;
import java.util.Deque;

class Solution {

    public boolean isValid(String s) {
        // The stack stores opening brackets that have not been matched yet.
        Deque<Character> stack = new ArrayDeque<>();

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

            // Save each opening bracket so a later closing bracket can match it.
            if (ch == '(' || ch == '[' || ch == '{') {
                stack.push(ch);
            } else {
                // A closing bracket is invalid when no unmatched opening bracket exists.
                if (stack.isEmpty()) {
                    return false;
                }

                // The most recent unmatched opening bracket must be matched first.
                char top = stack.pop();

                // Reject the string immediately if the closing and opening types differ.
                if (
                    (ch == ')' && top != '(') ||
                    (ch == ']' && top != '[') ||
                    (ch == '}' && top != '{')
                ) {
                    return false;
                }
            }
        }

        // The string is valid only when every opening bracket was matched.
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        // Run the same verified example shown in the diagram.
        String s = "([{}])";
        Solution solution = new Solution();
        System.out.println(solution.isValid(s)); // true
    }
}
Time & Space Complexity

Let n be the number of characters in the string. The time complexity is O(n) because we process each character at most once. Each push and pop on ArrayDeque takes constant time. The auxiliary space is O(n) because, in the worst case, the string can contain only opening brackets and all of them must remain in the stack. Auxiliary space means the extra memory used by the algorithm.

Where it is used

This stack pattern is useful when software must check nested structures. Examples include validating brackets in source code, parsing expressions, checking configuration syntax, and processing nested structured text where the most recently opened item must be closed first.

Why Interviewers Ask This

This problem tests whether a candidate recognizes a natural stack pattern and can maintain a simple invariant while scanning a string. The interviewer can check whether the candidate handles nesting order, empty-stack failures, mismatched bracket types, and unmatched opening brackets correctly. It also tests clean Java use of Deque and ArrayDeque, early-return reasoning, edge-case awareness, and the ability to explain the O(n) time and O(n) auxiliary space clearly.

Common interview mistakes

A common mistake is checking only how many opening and closing brackets exist. That does not detect wrong nesting such as "([)]". Another mistake is popping before checking whether the stack is empty. Candidates may also forget to compare the bracket types, so a ')' could incorrectly match '[' or '{'. Another mistake is forgetting the final stack-empty check, which would incorrectly accept a string such as "(". The stack must always use LIFO order so the newest unmatched opening bracket is checked first.

Interview tip

State the stack invariant before coding: the stack contains the unmatched opening brackets, and its top is the opening bracket that the next closing bracket must match.

Interviewer may ask next
Can this solution validate brackets from a stream instead of receiving the whole string at once?

Yes. Keep the same stack while characters or chunks arrive. For every character, use the same push, empty-stack check, pop, and type-match rules. A mismatch can still return false immediately. When the stream ends, return true only if the stack is empty. The total time is O(n). The extra space is O(d), where d is the maximum number of unmatched opening brackets at one time, and this is O(n) in the worst case. The tradeoff is that a final true result cannot be known until the stream ends.

Can we reduce the auxiliary space below O(n)?

Not in the general case while keeping the input unchanged. The algorithm may need to remember many unmatched opening bracket types before their closing brackets appear. For example, a prefix can contain many opening brackets, so the stored state can grow with n. The stack solution therefore still uses O(n) auxiliary space in the worst case. The time remains O(n). The tradeoff is that using less memory would lose information needed to verify the exact nesting order.

9. How would you solve 3Sum?CodingMediumApple

Question Details

Find all unique triplets that sum to zero and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would sort the array first, then fix one number as an anchor and use two pointers on the remaining sorted part. If the sum is too small, I move the left pointer right. If it is too large, I move the right pointer left. When the sum is zero, I save the triplet and skip duplicate values. This finds all unique triplets in O(n^2) time, with O(log n) auxiliary space from Java's primitive array sort.

Detailed Explanation

See the Code while reading this explanation.

The input is an integer array, and the goal is to return every different group of three values whose sum is zero. The same triplet must not appear more than once. I first sort the numbers. Then I choose one number as an anchor and search the remaining part with a left and right pointer. Sorting makes pointer movement predictable. It also makes duplicate values easy to skip. For the example [-1, 0, 1, 2, -1, -4], the result is [[-1, -1, 2], [-1, 0, 1]].

Useful Questions to Ask the Interviewer
  1. Should the returned triplets contain values rather than original indices?
  2. Can the input contain duplicate, negative, and zero values?
  3. Is modifying the input by sorting acceptable?
How would you solve 3Sum? diagram
How to Explain It in an Interview
1. Understand the input and required output

We receive an integer array and return unique triplets of values. We are not returning original indices. For the diagram example, the input is [-1, 0, 1, 2, -1, -4]. One valid returned result is [[-1, -1, 2], [-1, 0, 1]]. Each triplet sums to zero, and duplicate triplets are removed.

2. Choose sorting and two pointers

I sort the array in ascending order. The sorted values are [-4, -1, -1, 0, 1, 2]. For each anchor nums[i], I place left at i + 1 and right at the last index. The important invariant is that, for a fixed anchor, left and right scan the remaining sorted suffix. Moving left increases the sum. Moving right decreases the sum. This lets us find the needed pairs efficiently for each anchor.

3. Initialize the state

The result list starts empty. Traversal begins at i = 0. For each distinct anchor, left starts one position after the anchor and right starts at the end. If i > 0 and nums[i] equals nums[i - 1], I skip that anchor because it would repeat triplets already found. If nums[i] is positive, I stop the outer loop because every later value is also positive, so three values can no longer sum to zero.

4. Walk through the example

After sorting, the array is [-4, -1, -1, 0, 1, 2].

For i = 0, the anchor is -4. left = 1 and right = 5. The sum is -4 + -1 + 2 = -3, so I move left rightward. At left = 2, the sum is again -3. At left = 3, the sum is -2. At left = 4, the sum is -1. Each sum is too small, so left keeps moving right. No zero-sum pair exists for anchor -4.

For i = 1, the anchor is -1. left = 2 and right = 5. The sum is -1 + -1 + 2 = 0, so I add [-1, -1, 2]. Then I move both pointers inward. Now left = 3 and right = 4. The sum is -1 + 0 + 1 = 0, so I add [-1, 0, 1]. Moving both pointers inward ends this anchor's search.

At i = 2, nums[2] is -1 and equals nums[1], so I skip this duplicate anchor. At i = 3, the anchor is 0. left = 4 and right = 5. The sum is 0 + 1 + 2 = 3, so I move right leftward. left and right then meet, so the search ends. The final result is [[-1, -1, 2], [-1, 0, 1]].

5. Explain why the result is correct

For each distinct anchor, the remaining values are sorted. If the current sum is too small, moving left is safe because moving to a larger left value is the only pointer move that can increase the sum. If the current sum is too large, moving right is safe because moving to a smaller right value decreases the sum. When the sum is zero, we record the triplet. Skipping repeated anchors and repeated neighboring pointer values prevents duplicate triplets without removing a new unique result.

6. Explain the Java implementation

The code sorts nums first. The outer loop chooses each distinct anchor. It stops when the anchor becomes positive. For each anchor, a while loop runs while left < right. It calculates nums[i] + nums[left] + nums[right]. A zero sum is added to the result. Both pointers then move inward, and repeated neighboring values are skipped. A negative sum moves left. A positive sum moves right. Finally, the method returns every unique triplet found.

7. Explain complexity and edge cases

Sorting takes O(n log n). The outer loop combined with the two-pointer scan takes O(n^2), so the total time is O(n^2). Under the diagram's Java primitive-array sorting model, the auxiliary space is O(log n), excluding the returned output. Important cases are arrays with fewer than three values, [0, 0, 0], repeated numbers, and arrays containing only positive or only negative values.

Key Insight / Why This Solution Works

The key idea is to sort first and then turn each anchor position into a two-pointer search. For a fixed nums[i], left begins at i + 1 and right begins at n - 1. The central invariant is that left and right remain inside the sorted suffix to the right of the anchor. If the sum is below zero, moving left rightward increases the sum. If the sum is above zero, moving right leftward decreases the sum. When the sum is zero, the triplet is recorded. Duplicate anchors and duplicate neighboring pointer values are skipped so each value triplet appears only once.

Code
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {

    public static List<List<Integer>> threeSum(int[] nums) {
        // Store the unique zero-sum triplets found by the scan.
        List<List<Integer>> result = new ArrayList<>();

        // Sort so pointer movement changes the sum in a predictable direction.
        // Sorting also places duplicate values next to each other.
        Arrays.sort(nums);

        // Fix one value as the anchor. Two values must remain to its right.
        for (int i = 0; i < nums.length - 2; i++) {
            // Skip a repeated anchor because it would recreate earlier triplets.
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            // Because the array is sorted, a positive anchor means every
            // remaining value is positive, so no later triplet can sum to zero.
            if (nums[i] > 0) {
                break;
            }

            // Search the sorted suffix using one pointer at each end.
            int left = i + 1;
            int right = nums.length - 1;

            while (left < right) {
                // Calculate the sum represented by the anchor and two pointers.
                int sum = nums[i] + nums[left] + nums[right];

                if (sum == 0) {
                    // Record the valid triplet of values.
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    // Move both pointers inward before searching for another pair.
                    left++;
                    right--;

                    // Skip repeated left values so this triplet is not duplicated.
                    while (left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }

                    // Skip repeated right values for the same reason.
                    while (left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                } else if (sum < 0) {
                    // The sum is too small. A larger left value can increase it.
                    left++;
                } else {
                    // The sum is too large. A smaller right value can decrease it.
                    right--;
                }
            }
        }

        // Return all unique triplets discovered by the scans.
        return result;
    }

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

        // Prints: [[-1, -1, 2], [-1, 0, 1]]
        System.out.println(threeSum(nums));
    }
}
Time & Space Complexity

Let n be the number of values. Sorting costs O(n log n). For each anchor, the left and right pointers move inward through the remaining suffix, so the total two-pointer work across all anchors is O(n^2). O(n^2) dominates O(n log n), so the total time is O(n^2). Under the diagram's Java primitive-array sorting model, sorting uses O(log n) auxiliary stack space. The two-pointer search itself uses only a few variables. The returned list is output space and is excluded from that auxiliary-space figure.

Where it is used

This sorting and two-pointer pattern is useful when values must be combined to reach a target and sorting is allowed. Closely related patterns appear in pair-sum searches on sorted arrays, closest-sum problems, and other problems where increasing one pointer predictably raises the current sum while decreasing the other predictably lowers it.

Why Interviewers Ask This

This problem tests whether you recognize the sorting plus two-pointer pattern and can apply it repeatedly around a fixed anchor. It also tests careful duplicate handling, maintaining the left-right invariant, and explaining why each pointer movement is safe. The interviewer can evaluate whether you write correct Java collection code, handle edge cases such as repeated values and zeros, and explain the O(n^2) time and sorting-related auxiliary space accurately.

Common interview mistakes

A common mistake is forgetting to sort before using the pointer movement rules. Another is moving the wrong pointer: when the sum is negative, left must move rightward, and when the sum is positive, right must move leftward. Candidates also often forget to skip a repeated anchor such as the second -1. After recording a valid triplet, repeated left and right values must be skipped too. Another mistake is returning original indices instead of the triplet values required here. Finally, sorting cost and sorting stack space should be included in the complexity discussion.

Interview tip

Explain the pointer rule before writing the loop: after sorting, a sum below zero means move left, a sum above zero means move right, and a zero sum means record the triplet, move both pointers inward, and skip duplicates. That one rule makes both the code and the correctness argument easier to follow.

Interviewer may ask next
What changes if the input is already sorted?

The sorting step can be removed. The anchor loop, duplicate skipping, left and right pointer positions, sum checks, and pointer movements stay the same. The two-pointer invariant is already available because the input is sorted. The total time remains O(n^2), because the nested anchor and two-pointer work still dominates. Auxiliary search space becomes O(1), excluding the returned triplets, because no sorting stack is needed. The tradeoff is that this only applies when sorted input is guaranteed.

How would the solution change if the target were a value other than zero?

The structure stays the same. Sort the array, choose each distinct anchor, and use left and right pointers. Instead of comparing nums[i] + nums[left] + nums[right] with zero, compare it with the requested target. If the sum is smaller than the target, move left rightward. If it is larger, move right leftward. If it equals the target, record the triplet and skip duplicates. The same sorted-order invariant preserves correctness. Time remains O(n^2), and auxiliary space remains O(log n) under the same sorting model.

10. How would you solve Add Two Numbers?CodingMediumApple

Question Details

Add two numbers represented as linked lists and explain your approach, edge cases, and complexity.

Short Interview Answer (30-60 seconds)

I would add the two linked lists one digit at a time while carrying any overflow to the next position. I keep two pointers, one for each list, plus a dummy head for the result and an integer carry. At each step, I add the two current values and the carry, append sum % 10, and update the carry with sum / 10. I continue until both lists and the carry are finished. The time is O(max(m, n)), with O(1) auxiliary space excluding the returned list.

Detailed Explanation

See the Code while reading this explanation.

Each list stores one digit in each node. The digits are in reverse order, so the first nodes represent the ones place. We need to add the two numbers and build a new list in the same order. I add matching digit positions together and keep any carry for the next position. This fits the input well because we can move through both lists from the beginning. We do not need to reverse the lists or convert them into normal integer values.

Useful Questions to Ask the Interviewer
  1. Are the digits stored in reverse order, with the ones digit first?
  2. Should I return a new linked list instead of changing the input lists?
  3. Can the two lists have different lengths?
How would you solve Add Two Numbers? diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is two linked lists. Each node contains one digit. The digits are stored in reverse order. For example, 2 -> 4 -> 3 represents 342, and 5 -> 6 -> 4 represents 465. We must return a new linked list containing the sum in the same reverse order. For this example, 342 + 465 = 807, so the result is 7 -> 0 -> 8, or [7, 0, 8].

2. Choose the algorithm and result structure

I use two pointers, p1 and p2, to walk through the input lists together. I also keep an integer carry. A dummy node starts the result list, and tail always points to its last node. Reverse-order storage is useful because the first nodes are the least significant digits, which is exactly where normal addition starts.

The central invariant is: dummy.next contains the correct result digits for every position already processed, and carry contains the overflow that must be added to the next digit position.

3. Initialize the state

Set p1 = l1 and p2 = l2. Create dummy = new ListNode(0) and set tail = dummy. Set carry = 0. At this point, the result is empty because dummy.next is null. Traversal begins at values 2 and 5 in the example.

4. Walk through the example

Step 1 starts with p1.val = 2, p2.val = 5, and carry = 0. The calculation is 2 + 5 + 0 = 7. The new digit is 7 % 10 = 7, and the new carry is 7 / 10 = 0. Append node 7. The result is now 7. Move both pointers forward.

Step 2 uses values 4 and 6 with carry 0. The calculation is 4 + 6 + 0 = 10. The digit is 10 % 10 = 0, and the carry becomes 10 / 10 = 1. Append node 0. The result is now 7 -> 0. Move both pointers forward.

Step 3 uses values 3 and 4 with carry 1. The calculation is 3 + 4 + 1 = 8. The digit is 8 % 10 = 8, and the carry becomes 8 / 10 = 0. Append node 8. The result is 7 -> 0 -> 8. Both pointers are now null and carry is 0, so the loop stops. All three digit positions have been processed.

5. Explain why the result is correct

Each loop iteration performs normal addition for exactly one digit position. sum % 10 gives the digit that belongs in the current position. sum / 10 keeps any overflow for the next position. Since both pointers advance one node at a time when possible, every position is processed in order. When both lists are finished and carry is 0, all required positions have been handled, so dummy.next is the correct sum list.

6. Explain the Java implementation

The loop continues while p1 has a node, p2 has a node, or carry is not zero. If one list ends first, its missing digit is treated as 0. After calculating the new digit and carry, the code creates one new result node and moves tail. It then advances each input pointer when that pointer is not null. Finally, it returns dummy.next, which skips the temporary dummy node.

7. Explain complexity and edge cases

If the list lengths are m and n, the time is O(max(m, n)) because each input node is processed at most once, with at most one extra iteration for a final carry. The algorithm uses O(1) auxiliary space because it keeps only a few pointers and integer variables. The returned list itself can contain O(max(m, n) + 1) nodes. Important edge cases are different list lengths, a final carry such as 9 -> 9 plus 1, zero values, and single-node lists.

Key Insight / Why This Solution Works

The key idea is to perform normal elementary addition directly on the linked lists. Because the digits are stored in reverse order, traversal already starts at the ones place. Two pointers read the current digits. If one pointer has reached the end, its digit is treated as 0. The algorithm adds both digits and the incoming carry, appends sum % 10, and keeps sum / 10 as the next carry. A dummy head makes result construction simple. The invariant is that dummy.next always contains the correct digits for all processed positions, while carry stores the overflow for the next position.

Code
public class Main {

    // A node stores one digit and a reference to the next digit node.
    static class ListNode {

        int val;
        ListNode next;

        ListNode(int val) {
            this.val = val;
        }
    }

    static class Solution {

        public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            // Use a dummy head so the first result node needs no special case.
            ListNode dummy = new ListNode(0);
            ListNode tail = dummy;

            // Carry stores overflow that must be added at the next digit position.
            int carry = 0;

            // Traverse the two input lists from their least significant digits.
            ListNode p1 = l1;
            ListNode p2 = l2;

            // Continue while either list has a digit or a final carry remains.
            while (p1 != null || p2 != null || carry != 0) {
                // Treat a missing digit as 0 when one list ends before the other.
                int x = p1 != null ? p1.val : 0;
                int y = p2 != null ? p2.val : 0;

                // Add the two current digits and the incoming carry.
                int sum = x + y + carry;

                // Save overflow for the next position and keep this position's digit.
                carry = sum / 10;
                int digit = sum % 10;

                // Append exactly one result node for the current digit position.
                tail.next = new ListNode(digit);
                tail = tail.next;

                // Move each input pointer forward only when a node is available.
                if (p1 != null) {
                    p1 = p1.next;
                }
                if (p2 != null) {
                    p2 = p2.next;
                }
            }

            // Return the real result head, not the temporary dummy node.
            return dummy.next;
        }
    }

    public static void main(String[] args) {
        // Build l1 = [2, 4, 3], which represents 342 in reverse order.
        ListNode l1 = new ListNode(2);
        l1.next = new ListNode(4);
        l1.next.next = new ListNode(3);

        // Build l2 = [5, 6, 4], which represents 465 in reverse order.
        ListNode l2 = new ListNode(5);
        l2.next = new ListNode(6);
        l2.next.next = new ListNode(4);

        // Run the same digit-by-digit addition algorithm shown in the diagram.
        Solution solution = new Solution();
        ListNode result = solution.addTwoNumbers(l1, l2);

        // Print the diagram's expected result: [7, 0, 8].
        System.out.println(toListString(result));
    }

    static String toListString(ListNode head) {
        // Convert the result list to readable list notation without modifying it.
        StringBuilder output = new StringBuilder("[");
        ListNode current = head;

        while (current != null) {
            output.append(current.val);
            if (current.next != null) {
                output.append(", ");
            }
            current = current.next;
        }

        output.append("]");
        return output.toString();
    }
}
Time & Space Complexity

Let m be the number of nodes in the first list and n be the number of nodes in the second list. The time complexity is O(max(m, n)) because each input node is processed at most once, and there can be at most one additional iteration for a final carry. The auxiliary space is O(1) because the algorithm only keeps a few pointers and integer variables. This does not count the returned linked list. The output list can contain up to O(max(m, n) + 1) nodes because a final carry may create one extra node.

Where it is used

This pattern is useful when numbers are stored as linked sequences of digits instead of one built-in integer value. It lets software perform arithmetic directly on the stored digits and build a new result list without first reversing the lists or converting the complete values into primitive integer types.

Why Interviewers Ask This

This problem checks whether you can combine linked-list traversal with simple arithmetic state. The interviewer can see whether you manage multiple node references safely, preserve a carry across iterations, handle lists of different lengths, and build a result list without losing references. It also tests whether you can maintain and explain an invariant, choose the correct stopping condition, write correct Java code, and state time and auxiliary space complexity accurately.

Common interview mistakes

A common mistake is forgetting the carry after a sum such as 4 + 6 = 10. Another mistake is stopping when both input pointers become null even though a final carry may remain. Candidates may also forget to use 0 after one list ends, which breaks different-length inputs. Another mistake is advancing a pointer without checking whether it is null. Finally, returning dummy instead of dummy.next incorrectly includes the temporary zero node in the result.

Interview tip

While coding, say the invariant out loud: the result list already contains the correct digits for every processed position, and carry is the overflow that belongs to the next position. This makes the loop condition, digit calculation, and stopping condition easy to justify.

Interviewer may ask next
What happens if the two linked lists have different lengths?

The algorithm does not need to change. When one pointer becomes null, its digit is treated as 0 while the other list continues. We still add the remaining digit and the current carry, append the result digit, and advance the pointer that still has nodes. Correctness is preserved because every remaining digit position is processed in order. The time remains O(max(m, n)), and auxiliary space remains O(1) excluding the returned list.

What happens if there is a carry after both input lists end?

The existing loop condition already handles it because the loop continues while carry != 0. For example, 9 -> 9 plus 1 produces 0 -> 0 -> 1. After the last input nodes are consumed, one extra iteration creates a node from the remaining carry. This preserves normal addition rules. The time remains O(max(m, n)), and auxiliary space remains O(1) excluding the returned list.

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.