Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Maximize (sum of a contiguous subarray) mod m. Work with prefix sums taken mod m: a subarray (l..r) has value (pre[r] − pre[l−1] + m) mod m. To make that big you want pre[l−1] to be the *smallest prefix strictly greater* than pre[r] — then the result is close to m−1. Sweep left to right keeping prior prefixes in an ordered set (TreeSet); at each r take max(pre[r], (pre[r] − higher(pre[r]) + m) mod m). O(n log n). Take the running sum mod m every step and use long, or it overflows.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you express the modular subarray value via prefix sums and realize that maximizing (pre[r] − pre[l−1]) mod m means finding the smallest prefix strictly greater than pre[r] — an ordered-set / binary-search query.
Where candidates slip
Accumulating the raw sum and overflowing, forgetting the +m before the final mod (negative result), or scanning all earlier prefixes for an O(n²) blowup.
How to stand out
Explain why 'smallest prefix greater than pre[r]' maximizes the wrapped value, mention that pre[r] alone covers the prefix-starting case, and call out taking mod each step plus using long before being prompted.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewpre = (pre + x) mod ms exists?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — All Subarrays
1Hint 1Explore hintHide hint
What's the most direct way to evaluate every contiguous subarray's sum mod m?
2Hint 2Explore hintHide hint
Fix the left end l, extend the right end r one step at a time, keeping a running sum reduced mod m.
3Hint 3Explore hintHide hint
Track the maximum (running sum) mod m across all (l, r).
A · Core idea
The literal approach: enumerate every subarray. Fix a left endpoint l, walk the right endpoint r rightward, maintain the running sum reduced mod m, and keep the maximum residue seen.
Reduce mod m as you extend. Don't store the raw running sum — with a[i] up to 10¹⁸ it overflows almost immediately. Keep cur = (cur + a[r]) % m; the maximum of cur over the inner loop is the best residue for that left endpoint.
Why it's the natural baseline. The objective is defined over subarrays, so looping over all subarrays is the obvious correct thing. It's fine for small n and confirms the answer when testing the optimal version.
Why we don't ship it. n up to 10⁵ gives ~5×10⁹ subarray steps — too slow. Prefix sums plus an ordered set collapse it to O(n log n).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
a = [3, 3, 9, 9, 5], m = 7Array state
l=0, r=1
sum 6, 6 mod 7 = 6
6C · Code with teaching comments
Reference implementation previewpublic long maxSubarrayModBrute(long[] a, long m) { int n = a.length; long best = 0; for (int l = 0; l < n; l++) { long cur = 0;Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Re-summing overlapping subarrays is wasted work: the sum of (l..r) and (l..r+1) differ by one element. That overlap is the cue to switch to prefix sums, where any subarray sum is a single subtraction.
ComplexityO(n²) time, O(1) space — For each of n start points you extend up to n endpoints → n²/2 subarrays, O(n²).
E · Edge cases and pitfalls3Storing the raw running sum and overflowing — reduce mod m each step.Explore edge casesHide edge cases
Pitfalls
- Storing the raw running sum and overflowing — reduce mod m each step.
- Resetting cur incorrectly between left endpoints.
- Running O(n²) on n = 10⁵ and timing out.
Approach 2 of 2 · Optimal
Prefix Sums + Ordered Set (Optimal)
1Hint 1Explore hintHide hint
Write the subarray (l..r) value as (pre[r] − pre[l−1] + m) mod m. For a fixed r, which earlier prefix makes this largest?
2Hint 2Explore hintHide hint
If you pick pre[l−1] just slightly greater than pre[r], the subtraction goes negative and +m wraps it near m − 1 — the biggest possible.
3Hint 3Explore hintHide hint
Maintain earlier prefixes in an ordered set; for each pre[r] query the smallest stored value strictly greater than pre[r]. Also consider pre[r] itself.
A · Core idea
Define pre[k] as the sum of the first k elements mod m. Any subarray (l..r) has value (pre[r+1] − pre[l] + m) mod m. Sweep left to right computing the running prefix pre (mod m at every step), and keep all earlier prefixes in an ordered set.
Why 'smallest prefix strictly greater than pre[r]'. We want (pre[r] − pre[l]) mod m as large as possible. If pre[l] ≤ pre[r], the difference is in [0, pre[r]] — at most pre[r], already covered by the candidate pre[r] itself (subtracting nothing, i.e. the subarray from the start). The only way to beat that is to make the difference negative and let + m wrap it: pick pre[l] > pre[r], giving pre[r] − pre[l] + m = m − (pre[l] − pre[r]). To make m − (pre[l] − pre[r]) large, minimize pre[l] − pre[r], i.e. choose the smallest pre[l] that is strictly greater than pre[r]. That's exactly an ordered-set 'higher' / upper-bound query.
Why pre[r] itself is a candidate. The subarray starting at index 0 and ending at r has value pre[r] (nothing subtracted). Including pre[r] in the max covers that case for free, so we never need to store a sentinel 0 explicitly.
Why mod every step + long. With a[i] up to 10¹⁸ and n up to 10⁵, the raw sum reaches 10²³ — long overflows. Reducing mod m each step keeps pre in [0, m). The wrap pre − s + m reaches about 2×10¹⁴ for m near 10¹⁴, still safely inside 64-bit. Forgetting the + m before the final % m yields a negative result and a wrong answer.
Why it's fast. Insert and 'higher' query are O(log n) in a balanced ordered set (Java TreeSet). One of each per element gives O(n log n) — comfortable for 10⁵.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
a = [3, 3, 9, 9, 5], m = 7Array state
after x=3,3
pre=6 is the maximum residue seen
6C · Code with teaching comments
D · Why it works and complexityO(n log n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The modular objective rewards a prefix that's just above the current one — turning 'maximize a wrapped difference' into a clean 'find the successor of pre[r]' query. Prefix sums telescope the subarray into a single subtraction, and an ordered set makes the successor lookup logarithmic. This 'prefix sum + ordered structure' duo recurs in count-of-range-sum and similar problems.
ComplexityO(n log n) time, O(n) space — Each element does one ordered-set insert and one 'just greater' query, both O(log n) → O(n log n) total. The set holds up to n prefixes → O(n) space.
E · Edge cases and pitfalls5a = [m, 2m, 3m], m arbitrary — Every prefix residue is 0; no higher prefix exists, best stays 0. Multiples of m contribute nothing.Explore edge casesHide edge cases
Edge cases handled
a = [m, 2m, 3m], m arbitraryEvery prefix residue is 0; no higher prefix exists, best stays 0. Multiples of m contribute nothing.a = [m-1], mpre = m−1; best = m−1, the maximum possible residue from a single element.
Pitfalls
- Omitting the + m before the final % m — a negative dividend gives a wrong (often negative) result.
- Querying for the largest smaller prefix instead of the smallest greater one — that doesn't maximize the wrap.
- Letting the raw sum grow unbounded — reduce mod m each step and keep long.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic long maxSubarrayMod(long[] a, long m) { java.util.TreeSet<Long> seen = new java.util.TreeSet<>(); long pre = 0, best = 0; for (long x : a) { pre = (pre + x % m) % m; // reduce every step — no overflowExplore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Negative residue from missing + m
pre − higher is negative when higher > pre. Taking % m directly on a negative value yields a negative result in Java.
Explore the code comparisonHide the code comparison
Negative residue from missing + m
pre − higher is negative when higher > pre. Taking % m directly on a negative value yields a negative result in Java.
Don't write this
best = Math.max(best, (pre - higher) % m);Write this instead
best = Math.max(best, (pre - higher + m) % m);2Overflow from un-reduced sums
a[i] up to 10¹⁸ over 10⁵ elements reaches 10²³ — long overflows. Reduce mod m at every step.
Explore the code comparisonHide the code comparison
Overflow from un-reduced sums
a[i] up to 10¹⁸ over 10⁵ elements reaches 10²³ — long overflows. Reduce mod m at every step.
Don't write this
pre = pre + x; // grows without bound
best = Math.max(best, pre % m);Write this instead
pre = (pre + x % m) % m; // stays in [0, m)3Wrong ordered-set query direction
Maximizing the wrapped value needs the smallest prefix strictly GREATER than pre. Picking the largest smaller prefix minimizes, not maximizes.
Explore the code comparisonHide the code comparison
Wrong ordered-set query direction
Maximizing the wrapped value needs the smallest prefix strictly GREATER than pre. Picking the largest smaller prefix minimizes, not maximizes.
Don't write this
idx = bisect.bisect_left(seen, pre) - 1 # largest <= preWrite this instead
idx = bisect.bisect_right(seen, pre) # smallest > preRevise
Carry the pattern into the interview
Pattern to remember
Prefix sums mod m + ordered-set 'smallest greater' query
Code pattern to memorize
pre = 0; best = 0; set = {}
for x in a:
pre = (pre + x) % m
best = max(best, pre)
s = set.higher(pre) # smallest stored > pre
if s exists: best = max(best, (pre - s + m) % m)
set.add(pre)
return bestRules to remember
- 1Subarray sum mod m =
(pre[r] − pre[l−1] + m) % m— the+ mkeeps it non-negative after subtraction. - 2To maximize that, subtract the smallest prefix that is strictly greater than
pre[r]; that pushes the wrapped result toward m−1. - 3
pre[r]itself is a valid candidate — it's the subarray starting at index 0, equivalent to subtracting nothing. - 4Reduce the running sum mod m at every step; never accumulate the raw sum, it overflows fast.
- 5Use long everywhere: m can be ~10¹⁴, so
pre − s + mreaches ~2×10¹⁴, beyond 32-bit.
Use this pattern when
- Maximize / optimize a subarray quantity that telescopes through prefix sums
- A modular objective where you need the closest prefix on one side (just-greater / just-smaller)
- Repeated 'smallest element greater than x' queries over a growing set
Choose another pattern when
- Plain maximum subarray sum (no modulo) → Kadane's algorithm, O(n), no ordered set
- You need the actual subarray indices, not just the value → track positions alongside
- m is tiny (say ≤ a few thousand) → a counting array over residues can beat the ordered set
Interview takeaway
Turn 'best subarray under mod' into 'for each prefix, find the smallest earlier prefix just above it'. Prefix sums make the subarray telescope, and an ordered set answers the just-greater query in O(log n), giving O(n log n) overall.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternPrefix sums plus an ordered set: telescope a subarray objective into a difference of prefixes, then answer a successor/predecessor query in O(log n). Here the modular max wants the smallest prefix just above the current one. Same toolbox as Count of Range Sum and other prefix-difference problems.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Maximum Subarray (Kadane)Without the modulo, the classic max subarray sum is Kadane's O(n) running max.
- Subarray Sum Equals KComing soonPrefix sums + hash map for counting subarrays with an exact sum.
- Count of Range SumComing soonPrefix sums + ordered structure / BIT to count sums in a range — same toolbox.