Senior Engineering Interview Handbook / Chapter 34
Writing Senior-Level Interview Code
A technical-foundation chapter on senior-level coding style in interviews: naming, decomposition, helper functions, explicit assumptions, comments, minimal abstraction, and practical extensibility.
Preparing audio…
Audio edition
Writing Senior-Level Interview Code
Page tools
The code passes. Can anyone inspect it?
Consider a familiar prompt:
Given a list of closed intervals, merge every pair that overlaps and return the merged intervals in increasing start order.
This solution has the right algorithm:
def solve(a):
if not a:
return []
a.sort()
r = []
s, e = a[0]
for x in a[1:]:
if x[0] <= e:
e = max(e, x[1])
else:
r.append([s, e])
s, e = x
r.append([s, e])
return r
It may pass every required test. It is still a poor artifact for a live review.
The reader must discover that the input is sorted in place, remember what six
short names mean, infer why touching endpoints merge, and notice that a[1:]
copies part of the list. If the interviewer changes the endpoint rule, the
candidate must find the policy inside the scan and explain whether anything
else depends on it.
Senior-level interview code does not need to resemble a production service. It needs to make a small solution inspectable: another engineer should be able to recover the algorithm, see its ownership decisions, and locate the effect of a likely change before the clock runs out.
Begin with the decisions that enter the code
The prompt leaves two decisions worth settling before implementation. First,
closed intervals that share an endpoint overlap, so [1, 3] and [3, 5]
become [1, 5]. Second, the function will not change the caller’s list.
That is enough clarification. Asking about every imaginable malformed record would only delay the problem unless invalid input is in scope. A concise statement gives the reviewer the boundary:
“I’ll assume each interval is valid. I’ll treat touching endpoints as overlapping and return a new list without reordering the caller’s input.”
The code must now keep those promises. In-place sort is no longer an innocent
implementation detail, and the comparison used for overlap is policy rather
than punctuation.
Name state by what it means
The first improvement is not a helper or a class. It is vocabulary:
sorted_intervals = sorted(intervals, key=lambda interval: interval[0])
current_start, current_end = sorted_intervals[0]
merged = []
The names expose the state of the scan. current_start and current_end
describe the interval that covers all overlapping input seen since the last
output interval. merged holds intervals whose end can no longer change.
sorted_intervals makes the ownership choice visible: the function has its own
outer list to reorder, while the caller’s list remains in its original order.
Names need not be long. left and right are excellent names for search
boundaries; row and col are clear within a grid traversal. Brevity becomes
a problem when it removes the role. r tells the reader that something is
stored. merged tells the reader what has already been established.
Extract the decision, not the motion
The endpoint rule deserves a name because it may change independently of the scan:
def overlaps(current_end, next_start):
return next_start <= current_end
This helper shortens the review argument: the main loop maintains a merged
range, while overlaps defines which next interval belongs to it. If the
interviewer later says that touching intervals must remain separate, the
policy changes from <= to < in one named place.
Not every group of lines earns extraction. Helpers named process, update,
or handle_case merely force the reader to jump elsewhere. A useful helper
usually names one of four things:
- a domain rule, such as
overlaps; - a boundary, such as
in_bounds; - a representation, such as
normalize_key; - an ownership event, such as copying a completed path.
The test is whether the helper makes the correctness explanation shorter. If the explanation now requires a tour through several tiny functions, leave the code inline.
Keep ownership where the reader can see it
The complete implementation is still one small function:
def merge_intervals(intervals):
if not intervals:
return []
# Sort a new outer list so the caller's interval order is unchanged.
sorted_intervals = sorted(intervals, key=lambda interval: interval[0])
def overlaps(current_end, next_start):
# Closed intervals that share an endpoint overlap.
return next_start <= current_end
interval_iter = iter(sorted_intervals)
current_start, current_end = next(interval_iter)
merged = []
for next_start, next_end in interval_iter:
if overlaps(current_end, next_start):
current_end = max(current_end, next_end)
continue
merged.append([current_start, current_end])
current_start, current_end = next_start, next_end
merged.append([current_start, current_end])
return merged
The iterator avoids creating the slice used in the first version. More important, each value has one visible responsibility. The active interval is mutable local state; completed intervals cross into the result as new lists. The input intervals themselves are read but never modified.
That last point is deliberately precise. sorted creates a new outer list; it
does not deep-copy each interval. A shallow copy is sufficient because the
function only reads the nested interval values. If the implementation later
normalizes endpoints by changing those nested lists, it would violate the
stated ownership promise.
The two comments remain because the syntax cannot tell the whole story. One
records an ownership decision and the other records an endpoint policy.
Comments such as # loop over intervals or # append the result would add no
information. A decision comment should explain why the line has this shape,
especially when a plausible alternative would behave differently.
Minimal abstraction leaves room to finish
It is possible to make the solution look more architectural by defining an
Interval class, a comparator strategy, and a merge service. None of those
objects is justified by this prompt. They introduce constructors, conversion,
lifecycle, and extension points that the reviewer must understand without
making the scan easier to prove.
Abstraction is useful when the problem supplies a durable boundary. A class is
natural when the prompt asks for a data structure that supports repeated
operations. A comparator is natural when ordering itself varies. A validation
layer is natural at an untrusted input boundary. A grid traversal may benefit
from a neighbors(row, col) helper because it centralizes the movement rules.
The interval problem supplies one changeable domain rule, so it gets one helper. Everything else stays close to the loop. That proportion is the design judgment.
The same restraint prevents accidental state. Suppose a one-shot prompt asks
for the first non-repeating character in a string. A
CharacterFrequencyService with an internal count map creates questions the
prompt never posed: whether instances are reusable, when counts reset, and
whether lookup is valid before ingestion. Two explicit passes inside one
function are easier to understand and cannot leak counts into the next call.
If the follow-up asks for a streaming interface, persistent state then becomes
a requirement rather than decoration.
Design for the follow-up you can name
Extensible interview code does not predict a family of future products. It keeps the important decisions local.
Return to the interval solution. The interviewer asks for half-open intervals,
where an interval ending at 3 does not overlap one starting at 3. The
change is small:
def overlaps(current_end, next_start):
# Half-open intervals that only touch do not overlap.
return next_start < current_end
The scan, names, ownership policy, and complexity remain intact. A boundary test must change too, but there is no architecture to renegotiate. The code is adaptable because the original implementation identified a real seam, not because it installed a general strategy framework in advance.
Other prompts have different seams. Diagonal grid movement belongs in the neighbor rule. A tie-break belongs in a comparison key. Input mutation belongs at the copy boundary. Invalid-record handling belongs before the core algorithm. The useful question is not “How can I make this generic?” It is “Which rule could change without changing the algorithm?”
Read the finished artifact as a reviewer
Before testing, take one quiet pass through the code in execution order. Check that the function name states the job, names still match their roles, comments still match the decisions, and no abandoned branch or debug print survives. Then inspect the library operations: sorting costs O(n log n), the new outer list costs O(n) space beyond the output, and the scan is linear. The iterator does not create another list.
This pass is not cosmetic. A stale name can preserve an obsolete model after a mid-interview change. A stale comment can make correct code look wrong. An unmentioned copy can invalidate the space analysis. Readability is part of the evidence because it lets both engineers find those disagreements while there is still time to repair them.
You can practice this without learning a new algorithm. Take an old solution and keep its behavior and complexity fixed. Rename state by role. State its mutation policy. Extract only the helper that most shortens the correctness argument. Delete comments that narrate syntax. Then introduce one plausible follow-up and see whether it changes a named decision or tears through the whole function.
The finished code need not advertise seniority. It should simply be easy to question. Chapter 33 established why the algorithm is correct; this chapter has made that reasoning visible in the artifact. The next task is to choose tests that challenge it.
Related reading
- Chapter 21: Thinking Aloud Without Narrating Noise
- Chapter 24: Recovering When Stuck
- Chapter 25: Communicating Trade-Offs Like a Senior Engineer
- Chapter 30: Interview-Language Fluency
- Chapter 31: Complexity and Constraint Analysis
- Chapter 32: The Coding-Round Workflow
- Chapter 33: Correctness Before Cleverness
- Chapter 35: Testing in the Interview
Continue reading
Full table of contents