Articles
Technical Interview Follow-Up Questions: Defend Your First Solution When Constraints Change
Prepare for technical interview follow-up questions by tracing changed constraints, defending invariants, and deciding when your first design needs revision.
Your first solution works. Then the interviewer changes the input from an array to a stream, reduces available memory, or asks whether two requests can update the same state. The next challenge is not to defend every line you wrote. It is to defend the contract—and identify which assumption no longer holds.
For technical interview follow-up questions, preserve what remains correct, name the changed constraint, and use a counterexample before proposing a revision. “I would use a heap” is weaker than explaining what the heap retains, what it discards, and whether that discarded information becomes necessary later.
Evidence boundary: The examples below are original practice exercises, not a universal hiring rubric. The top-k implementation and history counterexample were checked in Python 3.12.14. The quota example is a conceptual interleaving, not a measured concurrent-service test.
Use Explain Java follow-ups after solving coding problem to practice changing an implementation while explaining the affected assumptions.
What should your first response to a changed constraint be?
Restate the difference precisely. “The data is larger” might mean more total records, more distinct keys, larger individual values, or less memory per worker. Those changes do not imply the same replacement algorithm. Ask which dimension changed and which outputs remain mandatory.
Then identify the old dependency: “My implementation sorts the entire input, so it needs a finite collection before returning.” That is a factual limitation, not an apology. A solution can be appropriate for its original contract and unsuitable for a new one.
As an official example of interview expectations, Amazon's SDE II preparation guidance emphasizes testing, edge cases, and refining runtime complexity. That supports preparation around reviewable reasoning; it does not establish the follow-up sequence for every employer.
A useful editorial response pattern is: changed assumption, preserved invariant, smallest separating test, revised cost. Do not rush through all four as a memorized speech. Use them to make the next decision visible to the interviewer.
Establish a baseline before optimizing it
Consider this original contract: given finite integer values and a fixed nonnegative k, return the largest k occurrences in descending order. Keep duplicates; if fewer than k values exist, return all of them. Return an empty list for k equal to zero, and reject negative k.
Sorting a copy is a reasonable baseline. It is simple, preserves the caller's input, and lets you inspect the requested ranks. Its comparison-sort cost is O(n log n), with O(n) additional storage for the copied list. Do not claim constant extra memory for a solution that explicitly copies the input.
Now clarify what top-k means. Largest values is different from most frequent distinct values. The latter needs counts or an explicitly approximate alternative. A heap of two large integers cannot establish the two most common keys in an arbitrary stream.
Also specify ties. Returning occurrences means [5,5,-1] with k equal to two returns [5,5]. A set would violate the contract. Returning distinct values or stable original positions requires a different selection and tie policy.
Follow-up one: input becomes a one-pass stream
With fixed k, retain a min-heap of up to k values. Its root is the smallest retained candidate. A new value larger than the root replaces it; a smaller value cannot displace a member of the current largest k.
import heapq
def largest_k(values, k):
if k < 0:
raise ValueError("k must be nonnegative")
if k == 0:
return []
heap = []
for value in values:
if len(heap) < k:
heapq.heappush(heap, value)
elif value > heap[0]:
heapq.heapreplace(heap, value)
return sorted(heap, reverse=True)
The invariant is: after each consumed prefix, the heap contains its largest min(k, prefix length) occurrences. Prove the replacement rule against that invariant rather than asserting “heaps are good for streams.” Python's heapq documentation describes the minimum-root structure and replacement operations.
Selection costs O(n log k) for k greater than one, with O(k) retained values; k equal to one is a linear maximum scan. Producing descending output adds O(k log k). The output ordering step belongs in your cost statement even though it is smaller than processing a long stream.
For [9,2,7,10] and k equal to two, inspect the retained candidates:
| Incoming value | Retained values, shown ascending | Decision |
|---|---|---|
| 9 | 9 | Heap not full |
| 2 | 2, 9 | Heap not full |
| 7 | 7, 9 | Replace 2 |
| 10 | 9, 10 | Replace 7 |
The final output is [10,9]. This table shows values in sorted order for readability, not a promise that an arbitrary heap's internal list is globally sorted.
Follow-up two: increase k after old values were discarded
The interviewer now asks for the largest three values across everything seen so far. Changing a variable from two to three is not enough: the old heap discarded 7.
Two histories expose the information loss:
| Historical input | State retained for top two | Correct top three |
|---|---|---|
| 9, 2, 7, 10 | 10, 9 | 10, 9, 7 |
| 9, 2, 8, 10 | 10, 9 | 10, 9, 8 |
Both produce the same stored state but require different answers. No function of those two retained values alone can recover the missing third value. This is stronger than vaguely saying that the heap “may be inaccurate.”
Ask whether the new request includes past input or applies only from this moment onward. For historical exactness, options include replaying a durable source, retaining a larger known upper-bound pool from the beginning, or storing enough additional history. Each spends memory, storage, or recovery time.
Decreasing k is different: a larger retained pool can be trimmed. Increasing beyond the amount previously retained cannot recreate discarded information. That asymmetry is the central lesson, not a recommendation to retain every stream forever.
Follow-up three: the memory limit becomes a hard bound
State what the bound measures. O(k) values is not O(k) bytes when each record contains an arbitrarily large payload. Retaining identifiers and ranking keys can reduce memory, but retrieving the full records later introduces another source and failure boundary.
If k itself exceeds the allowed retained state, the exact streaming heap no longer meets both requirements. Discuss external storage, additional passes when replay is available, or an agreed approximation. Do not quietly change the task from exact largest values to a sketch and call it equivalent.
For top frequent distinct keys, total distinct-key count matters even when the requested k is tiny. Counting every key can require unbounded memory over an unbounded domain. Before naming an approximate heavy-hitter method, define its error contract and what evidence the consumer needs.
This is where an honest limitation helps: “The baseline is exact for fixed k with enough space for k ranking entries. Under the new byte cap, I need either bounded record size, replay, external state, or relaxed accuracy.” The choices are explicit and testable.
Follow-up four: two callers update shared state
Switch to an original quota case. There is one remaining token. Request A reads one, request B reads one, and both decide to allow a request before writing zero. The final counter may look valid while two requests were admitted.
The invariant is not simply “remaining never becomes negative.” It is that successful admissions never exceed the available allocation. A final-state assertion alone misses the extra effect.
| Step | Request A | Request B | Visible remaining |
|---|---|---|---|
| 1 | Reads 1 | — | 1 |
| 2 | — | Reads 1 | 1 |
| 3 | Allows; writes 0 | — | 0 |
| 4 | — | Allows; writes 0 | 0 |
Protect the check and decrement as one transition, not just the read. A process-local mutex can be a correct baseline for one shared in-process counter. It does not coordinate independent service processes. Python's threading lock reference describes a local mechanism, not distributed quota enforcement.
Before proposing a datastore operation, clarify who owns the quota and whether failures can leave the caller uncertain about admission. Atomic counter changes alone do not define retries, request identity, or refunds after cancellation.
How do you defend the original solution without becoming defensive?
Say which original assumptions made it appropriate. “Sorting was the clearest correct solution for the finite array and unrestricted memory. The streaming constraint removes the ability to wait for the full input, so I will keep only the ranked candidates.” That is a technical comparison rather than an attempt to win an argument.
If the original solution contained a real error under the original contract, acknowledge it and isolate the failing test. Do not blame the follow-up for a missing duplicate rule that was present from the beginning. Likewise, do not rewrite working code merely because a more elaborate design sounds senior.
Preserve regression tests from the first version. Add one test that separates old and new requirements: an iterator without indexing, dynamic k requiring discarded history, oversized records, or the two-admission interleaving. Explain what each test establishes and what remains untested.
When execution is unavailable, keep the conclusion narrower. A hand trace can demonstrate the information-loss counterexample; it cannot measure throughput or establish that your implementation handles every runtime exception. If you run code, report the environment and actual assertions rather than inventing a speed improvement.
What should your final revised answer contain?
Give the new contract, retained state, invariant, cost, and one limitation. For the streaming top-k version: fixed nonnegative k, duplicates retained, a min-heap of candidate occurrences, O(k) entries, and no historical recovery beyond the retained pool without another source.
For the quota version: define the admission effect, atomic ownership boundary, and retry behavior before claiming thread safety. The same-looking final counter can conceal a different number of successful operations, so observe both.
End by asking whether the revised trade-off meets the new requirement. An interview follow-up often leaves multiple acceptable designs; showing why a choice fits is more useful than guessing the interviewer's preferred library.
Which regression cases should survive the revision?
For largest-k occurrences, retain empty input, k equal to zero, duplicate values, negative values in the data, and fewer values than k. Reject negative k explicitly; it is a malformed parameter, not the same thing as a negative ranked value. These cases keep the original contract visible after the input becomes an iterator.
Then add the two histories that share a top-two state but require different top-three results. A passing ordinary case cannot establish historical recoverability. If the revised design relies on replay, test the requested replay boundary and repeated values rather than checking only that a replay call returned successfully.