Sequential user stories, one at a time. The interviewer picks 2 of the 4 tasks. Budget 45 minutes for the two, including the questions at the end.
Story: Multiplier.multiply(x, y) returns x * y for two ints without using the *
operator. Drive it with JUnit tests.
Acceptance Criteria:
- Positive, negative and mixed signs:
6 x 7 = 42,-6 x 7 = -42,-6 x -7 = 42 - Zero on either side gives zero
- Boundaries that fit:
Integer.MIN_VALUE x 1,-65536 x 32768 = Integer.MIN_VALUE - Overflow throws
ArithmeticException, likeMath.multiplyExact(Integer.MAX_VALUE x 2,Integer.MIN_VALUE x -1) - O(log n) shift-and-add, not a loop of
|y|additions
Hints:
- Russian peasant: while
b != 0, addawhenb's low bit is set, thena <<= 1,b >>= 1 - Do the work on
longmagnitudes:Math.abs(Integer.MIN_VALUE)is still negative as anint - The talabat interviewers valued the TDD approach: write the negative, zero and overflow tests first
Story: Build a set with push(value), pop() (removes and returns the last inserted value
still present), remove(value), intersect(otherSet) (returns a new set), values() (any order)
and orderedValues() (insertion order).
Acceptance Criteria:
pop()after pushinga, b, creturnsc, thenb- A duplicate push is a no-op: the value keeps its original position (decision; say it out loud and write the test)
pop()skips values that were removed;pop()on an empty set throwsNoSuchElementExceptionintersectreturns a new set and leaves both inputs unchangedvalues()andorderedValues()return snapshots the caller cannot use to modify the set
Hints:
- The interviewer cared about code quality and naming, not performance. Still, a
LinkedHashSetalready is "hash table plus doubly linked list", and in Java 21 it hasremoveLast()
Story: LruCache(capacity) with get(key) and put(key, value), both O(1). When full, a
put of a new key evicts the least recently used entry (LeetCode 146).
Acceptance Criteria:
- LeetCode example, capacity 2:
put(1,1),put(2,2),get(1),put(3,3)evicts 2,put(4,4)evicts 1 getandputboth count as a use; a miss changes nothingputon an existing key updates the value and does not evict- Capacity 0 or less is rejected; the cache never holds more than
capacityentries - Build it with a
HashMapplus your own doubly linked list, notLinkedHashMap - Stretch: 8 threads calling
getandputat once never corrupt the list or exceed capacity
Hints:
- Sentinel head and tail nodes remove every null check from the list code
- The node must store its key, or eviction cannot remove it from the map
Story: DinnerPlates(capacity): an unbounded row of stacks, each holding at most capacity
plates. push goes to the leftmost stack that is not full, pop takes from the rightmost stack
that is not empty, popAtStack(index) takes from that stack (LeetCode 1172). The reported goal
was a working solution with test cases.
Acceptance Criteria:
- LeetCode example, capacity 2: push 1..5,
popAtStack(0)is 2, push 20 and 21,popAtStack(0)is 20,popAtStack(2)is 21, thenpopgives 5, 4, 3, 1, empty - After
popAtStackopens gaps at 1 and 3, the next pushes fill 1, then 3, then a new stack popskips stacks emptied bypopAtStackpop/popAtStackwith nothing to take return an emptyOptionalInt(LeetCode uses -1)
Hints:
- A
TreeSet<Integer>(orPriorityQueue) of indices of stacks that are not full gives the leftmost in O(log n) - Trim empty stacks off the right end so
popalways takes from the last stack
- What is the time and space complexity of each operation you wrote?
- Task 2: can push, pop and remove all be O(1)? (
LinkedHashSet, or aHashMapto nodes of a doubly linked list) - Task 3: in real life, would you build this? (
LinkedHashMapwithaccessOrder = trueandremoveEldestEntry) - Task 3: how would you make the LRU cache thread-safe? (Every method
synchronizedon one lock. AReadWriteLockdoes not help, becausegetreorders the list and so is a write.Collections.synchronizedMapover aLinkedHashMapworks the same way. Striping or lock-free designs are where it gets hard.) - When would you use Caffeine in production instead? (Almost always for an in-process cache: concurrent without a global lock, size and time based eviction, near-optimal hit rate from W-TinyLFU, stats, async loading)
- Ask for the approach and its complexity before any code. A candidate who starts typing a loop
of additions for Task 1 should be asked what happens with
multiply(1, Integer.MAX_VALUE). - In Task 2, push on the duplicate-push decision: either answer is fine, silence is not.
- In Task 3, watch for the node that does not store its key; the eviction step exposes it.
- For Task 4, a working brute force with tests beats an unfinished
TreeSetversion. Ask for the optimisation once it is green.