Sequential user stories, one at a time. Budget 50 minutes for all five (Tasks 1-4 alone take 35). For a one-hour mock the interviewer picks two or three tasks; Task 5 stands on its own.
For every task: say the approach and its time and space complexity before you type, and ask about the edge cases (empty input, duplicates, negatives, no answer) before you decide them yourself.
Story: Given an array of ints, return the largest element. Then the second largest. Then the kth largest.
Acceptance Criteria:
largestandsecondLargesteach make a single pass over the array, O(n) time, O(1) space- Second largest means the second largest distinct value:
[5, 5, 3]gives3 - When there is no second largest (one element, or every element equal),
secondLargestreturnsOptionalInt.empty()rather than a magic value likeInteger.MIN_VALUE(which is also a legitimate element) kthLargestcounts duplicates, like LeetCode 215:[5, 5, 3]with k = 2 gives5. Note that this is deliberately different fromsecondLargest; say which one you are buildingkthLargestruns in O(n log k) time and O(k) space with a min-heap of size k- An empty array is rejected with
IllegalArgumentException, a null one withNullPointerException; k outside1..nis rejected - Works with negatives and with
Integer.MIN_VALUE/Integer.MAX_VALUE
Hints:
- Second largest: keep
maxandsecond, and a flag (orOptionalInt) for "second seen yet" - Kth largest: a
PriorityQueue<Integer>is a min-heap by default. Push each element, pop when the size exceeds k; the head is the answer
Story: Return the largest sum of any non-empty contiguous subarray (LeetCode 53), in O(n) time and O(1) extra space.
Acceptance Criteria:
[-2, 1, -3, 4, -1, 2, 1, -5, 4]gives6(the subarray[4, -1, 2, 1])- An all-negative array returns its largest element, not
0:[-3, -1, -2]gives-1 - An empty array is rejected with
IllegalArgumentException - Sums do not overflow: two
Integer.MAX_VALUEelements sum correctly (return along) - Stretch:
maxSubarrayalso returns the start and end indices (inclusive). When several subarrays tie, return the one that ends first, and among those the shortest
Hints:
- Kadane: at each index, either extend the running subarray or start a new one here, whichever is larger. Track the best seen so far
- Starting
bestat0is the classic bug that breaks the all-negative case
Story: Return all index pairs (i, j) with i < j whose values sum to K. The array may
contain duplicates.
The example as the candidate reported it: [1, 1, 2, 23, 4, 9, 13, 6, 9], K = 10 returns
[[0, 8], [1, 5]].
That example is ambiguous, and clarifying it is part of the exercise. Check it yourself:
1 + 9 sums to 10 at (0, 5), (0, 8), (1, 5) and (1, 8), and 4 + 6 at (4, 7). The
reported answer looks like "use each element at most once", but even under that reading (4, 7)
is missing, so the example was probably mis-remembered. A strong candidate asks before coding:
every pair, or each index at most once? Is the order of the result significant?
The spec for this exercise (every pair):
- Return every pair
i < jwithnums[i] + nums[j] == k, each index free to appear in many pairs, sorted byithenj - The example above therefore returns
[[0, 5], [0, 8], [1, 5], [1, 8], [4, 7]] - No pairs gives an empty list; an empty array gives an empty list; null is rejected
- An element never pairs with itself:
[5]with K = 10 gives nothing,[5, 5]gives[[0, 1]] [3, 3, 3]with K = 6 gives[[0, 1], [0, 2], [1, 2]]- The complement
k - nums[j]must not overflow:[Integer.MAX_VALUE, 1]with K =Integer.MIN_VALUEgives nothing - One pass with a
HashMapfrom value to the indices seen so far: O(n + p) time where p is the number of pairs returned (you cannot beat O(p) when you must return p pairs; with every element equal, p is n²/2) - Stretch:
countPairsreturns just the number of pairs in O(n) time, without building them
Hints:
- The map stores what you have already seen, so every pair is found exactly once, at its
j - Sorting the result costs O(p log p). To get
(i, j)order for free, scan right to left (the map then holds later indices) and reverse the list at the end - For the count, store a count per value instead of a list of indices
Task 4: Orders in a Time Window (10 min) (reported at Delivery Hero, 2026: binary search; the exact problem was not shared, this is our version)
Story: An OrderTimeline holds the placement times (epoch millis) of a restaurant's orders,
sorted ascending. Answer two questions in O(log n): the first order placed at or after time t,
and how many orders fall in the window [from, to).
Acceptance Criteria:
- The constructor rejects null and unsorted input, and copies the array (later changes to the caller's array do not leak in). Equal timestamps are allowed
firstAtOrAfter(t)returns the earliest timestamp>= t, orOptionalLong.empty()when every order is beforetcountInWindow(from, to)counts timestamps withfrom <= ts < to:fromis inclusive,tois exclusive, so back-to-back windows never count an order twice- Duplicates: with
[100, 200, 200, 200, 300],firstAtOrAfter(200)is200, the window[200, 201)holds 3 orders,[100, 200)holds 1 - Boundaries: a window entirely before or after the data gives 0, a window covering all of it
gives n, an empty timeline gives 0 and empty,
from == togives 0,from > tois rejected - Works at the extremes of
long(no(lo + hi) / 2overflow)
Hints:
- Write one helper,
lowerBound(t): the first index whose value is>= t(n if none). Both methods fall out of it: the window count islowerBound(to) - lowerBound(from) - Use a half-open search range
[lo, hi)andmid = (lo + hi) >>> 1
Task 5: Final Destination, Then the Streams Rewrite (15 min) (reported at Delivery Hero, 2025: live coding on "hashing map and hashing set"; before 2023: a HackerRank problem "like Destination City")
Story: A rider's route arrives as a list of legs [from, to], in any order. Together they
form one path with no loops. Return the final destination: the city the rider ends up in, which is
the only city with no outgoing leg (LeetCode 1436).
[["London", "New York"], ["New York", "Lima"], ["Lima", "Sao Paulo"]] gives "Sao Paulo".
Acceptance Criteria:
finalDestinationruns in O(n) time with aHashSetof origins, not O(n²) with a nested loop- Legs may arrive in any order; a single leg returns its
to - City names match exactly (case-sensitive)
- LeetCode promises valid input, a service cannot. Decide and test what happens when there is no
answer or more than one. The spec for this exercise: null list, leg or city throws
NullPointerException; an empty list throwsIllegalArgumentException; so does anything with not exactly one city lacking an outgoing leg: zero means a loop (A->B, B->A), two or more means a fork (A->B, A->C) or disjoint paths (A->B, C->D) - Say what the O(n) check does not catch: a valid path plus a separate loop
(
A->B, C->D, D->C) still has exactly one dead end
Follow-up 1 (5 min) (reported at Delivery Hero, undated: "a simple one but have to use java
lambda and collections... check if I am comfortable with latest Java features"):
Rewrite it as finalDestinationWithStreams, with streams and collectors. Same contract, same
tests: run both versions through one parameterised test.
Follow-up 2 (5 min) (predicted, in the same spirit): Group today's orders by zone.
ordersPerZone returns each zone's order count; busiestZone returns the zone with the most
orders.
ordersPerZoneiterates in zone-name order; zones with no orders are absentbusiestZoneof no orders isOptional.empty()(a quiet day is not an error)- Ties go to the zone name that sorts first. The answer must not depend on
HashMapiteration order: test it with many zones tied on one order each
Hints:
- Two passes: collect every
frominto a set, then thetothat is not in it is the answer. Collect the dead ends rather than returning the first one, so you can count them - Streams:
legs.stream().map(Leg::from).collect(Collectors.toSet()), then.map(Leg::to).filter(Predicate.not(origins::contains)).distinct().toList() - Busiest zone:
groupingBy(Order::zone, counting()), then stream the entry set and takemax(Map.Entry.comparingByValue()). For the tie-break,.thenComparingon the key, reversed, becausemaxkeeps the greatest.groupingBy(Order::zone, TreeMap::new, counting())gives a sorted map
- What is the time and space complexity of each task? Where did you trade space for time?
- Kth largest: min-heap of size k (O(n log k), O(k), works on a stream) vs quickselect (O(n)
average, O(n²) worst case unless the pivot is randomised or median-of-medians, mutates or copies
the array) vs sorting (O(n log n)). When would you pick each? What does
kclose tonchange? - Task 3 with a stream of values that never ends: what do you keep, and what if memory is bounded? (Count only; or bound the window of values you remember)
- Task 3 when the data does not fit in memory: sort externally and walk two pointers inward, or hash-partition both sides by value so each value and its complement land in the same partition, then solve each partition in memory
- Binary search off-by-one traps:
lo <= hivslo < hi,hi = midvshi = mid - 1, inclusive vs exclusive bounds,(lo + hi) / 2overflow, and returning "not found" vs an insertion point. How did your tests catch each one? - Task 5, streams vs loops: which version is easier to read, and for whom? Where do streams get
worse (checked exceptions, early exit, index access, a debugger stepping through lambdas, a
pipeline nobody can name)? On performance: both are O(n); the stream version has some per-element
overhead (lambdas, boxing with
counting()) that rarely matters next to I/O, so measure with JMH before choosing loops for speed. When wouldparallelStream()help here? (Almost never at this size, and never with a shared mutable collector) - Task 5: how would you fully validate that the legs form one simple path, still in O(n)? (Every city at most one leg out and one in, exactly one start, and a walk from the start covers all n legs.) And if legs arrive as a stream of events, one rider at a time?
groupingByreturns aHashMap. Why didbusiestZoneneed an explicit tie-break, and what would a test that ran only one tie in a small map have missed?
- Task 3 is the one to watch. Do not volunteer the ambiguity: the signal is whether the candidate checks the example against the input and asks before coding. If they build the "each index once" version after asking, that is a fine answer too; ask them to state the spec and make the tests match it
- If
secondLargestreturnsInteger.MIN_VALUEfor "none", ask what it returns for[Integer.MIN_VALUE, 5] - If Kadane starts
bestat0, hand them[-3, -1, -2] - For Task 4, ask for the duplicate test before they write the search. Most off-by-one bugs only show up with a run of equal timestamps at a window edge
- Task 5 is easy on purpose. The signal is whether the candidate asks what happens with bad input (a loop, two dead ends, no legs) instead of trusting the LeetCode guarantee, and whether the stream rewrite reuses the same tests. If they write a nested loop, ask for the complexity with 100,000 legs
- In the streams follow-up, listen for why, not just how: a candidate who can say when they would
keep the loop is stronger than one who streams everything. If
busiestZoneusesmax(comparingByValue())alone, give them two zones tied and ask whether the answer is stable