Python Coding
Algorithmic and data-handling tasks an analyst solves in plain Python.
18 questions
MiddleCodeVery commonSum revenue per day from a log file too big to load whole
Sum revenue per day from a log file too big to load whole
Iterate the file object line by line — Python reads it lazily, so memory stays flat. Parse each line's day and amount, adding the amount into a dict keyed by day. Never call read() or readlines(), which load the whole file at once.
Common mistakes
- ✗Calling read() or readlines(), loading the whole file
- ✗Splitting on byte boundaries and cutting a line in half
- ✗Reaching for pandas on a file larger than memory
Follow-up questions
- →Why does iterating the file object keep memory flat?
- →How would you handle a malformed line without crashing?
MiddleCodeVery commonReturn each user's first and last event in a single pass
Return each user's first and last event in a single pass
Keep a dict keyed by user. For each event, if the user is new store (ts, ts), otherwise widen the stored pair to (min(first, ts), max(last, ts)). One O(n) pass gives each user's earliest and latest time together — no sort, no per-user rescan.
Common mistakes
- ✗Sorting the whole list when a single min/max pass suffices
- ✗Filtering per user, rescanning the list O(n*u) times
- ✗Assuming input order matches timestamp order
Follow-up questions
- →How would you also return each user's event count?
- →What changes if two events share a timestamp?
MiddleCodeVery commonSplit a sorted event stream into sessions on a 30-minute gap
Split a sorted event stream into sessions on a 30-minute gap
Walk the sorted timestamps once, tracking the current session's start and the previous event. When the step to the next event exceeds the gap, close the session — last minus first — and start a new one. Flush the final session after the loop; it is one O(n) pass.
Common mistakes
- ✗Measuring the gap from the session start instead of the previous event
- ✗Bucketing by fixed clock windows rather than by inter-event gaps
- ✗Forgetting to flush the final session after the loop
Follow-up questions
- →How would you return event counts per session instead of durations?
- →What changes if the input is not guaranteed sorted?
JuniorCodeCommonCount how many user ids appear in both lists in linear time
Count how many user ids appear in both lists in linear time
Turn one list into a set, then count the other's distinct ids found in it — set membership is O(1), so the job is O(n + m). len(set(list_a) & set(list_b)) does it in one line. The trap is if x in list_b, which rescans the list each time.
Common mistakes
- ✗Using
if x in list_binside a loop, giving O(n*m) - ✗Forgetting to de-duplicate ids within each list
- ✗Sorting when a set intersection is simpler and faster
Follow-up questions
- →Why does converting to a set change the complexity class?
- →How would you also return the ids, not just the count?
JuniorCodeCommonCompute the overall conversion rate across segments of very different sizes
Compute the overall conversion rate across segments of very different sizes
Overall conversion is total conversions over total visitors — a size-weighted average — not the mean of the per-segment rates. Averaging the rates weights every segment equally, so a tiny high-rate segment skews the number; weighting by visitor count fixes it.
Open full question →Common mistakes
- ✗Averaging the per-segment rates as if the segments were equal size
- ✗Weighting by something other than visitor count
- ✗Ignoring that a small segment can swing the simple mean
Follow-up questions
- →When does the simple mean equal the weighted mean?
- →How would you weight by revenue instead of visitors?
MiddleCodeCommonCompute ARPU for a test group and its implicit control
Compute ARPU for a test group and its implicit control
Split users into test (those in test_users) and control (everyone else in gmv_by_user), then divide each group's summed GMV by its user count. The trap is the denominator: a zero-GMV user still counts as a user, so divide by the number of users, not the number of paying users.
Common mistakes
- ✗Excluding zero-GMV users from the denominator
- ✗Reporting one pooled ARPU for both groups
- ✗Forgetting control is everyone not in the test set
Follow-up questions
- →Why must a zero-GMV user stay in the denominator?
- →How would you extend this to ARPPU (paying users only)?
MiddleCodeCommonBootstrap a 95% confidence interval for the median order value
Bootstrap a 95% confidence interval for the median order value
Resample the values with replacement to size n, take each resample's median, and repeat a few thousand times. The 2.5th and 97.5th percentiles of those bootstrap medians are the 95% CI — no closed-form standard error required.
Common mistakes
- ✗Resampling without replacement or at the wrong size
- ✗Applying a normal-approximation formula to the median
- ✗Percentile-ing the raw values instead of the bootstrap medians
Follow-up questions
- →Why resample to exactly n rather than some smaller size?
- →How would the interval change if you doubled n_boot?
MiddleCodeCommonCompute a streaming mean and variance you cannot hold in memory
Compute a streaming mean and variance you cannot hold in memory
Welford keeps a running count, mean, and sum of squared deviations M2, updated per element in one O(1)-memory pass. Sample variance is M2 / (n - 1). It avoids the naive sum(x**2) - sum(x)**2 / n, which cancels catastrophically.
Common mistakes
- ✗Using sum of squares minus square of sum, which cancels catastrophically
- ✗Buffering the whole stream, defeating the O(1)-memory goal
- ✗Dividing by n instead of n-1 for a sample variance
Follow-up questions
- →Why does the naive two-sums formula lose precision?
- →How would you merge two independently-computed partial states?
SeniorCodeCommonMerge each user's overlapping subscription intervals and total the covered days
Merge each user's overlapping subscription intervals and total the covered days
Bucket the spans by user, then per user sort by start and sweep once: hold a current interval, extend its end when the next start overlaps, else close it (adding end - start + 1 days) and open a new one. Summing closed lengths counts each day once.
Common mistakes
- ✗Summing span lengths, double-counting overlapping days
- ✗Merging intervals across users instead of within each user
- ✗Sweeping without sorting each user's intervals first
Follow-up questions
- →How would half-open intervals
[start, end)change the arithmetic? - →What is the time complexity per user?
JuniorCodeOccasionalCount employees who worked at least the target hours
Count employees who worked at least the target hours
Count the elements meeting the threshold: sum(1 for h in hours if h >= target), or sum(h >= target for h in hours) since booleans count as 0/1. The comparison must be >= — exactly meeting the target counts.
Common mistakes
- ✗Using > instead of >= and dropping exact matches
- ✗Assuming the input list is sorted
- ✗Dividing total hours by target instead of counting
Follow-up questions
- →Why does
>=matter for hitting the target exactly? - →How would you also return who met it, not just how many?
JuniorCodeOccasionalReturn the first local minimum in a list
Return the first local minimum in a list
Scan interior indices 1..len-2 and return x[i] at the first i where x[i] < x[i-1] and x[i] < x[i+1]; return None if none match. The decision is endpoint handling: with only interior candidates, lists shorter than 3 have no local minimum. It is a single O(n) left-to-right pass.
Common mistakes
- ✗Confusing the global minimum with the first local one
- ✗Checking only one neighbour instead of both
- ✗Mishandling the first/last elements as candidates
Follow-up questions
- →How would you treat endpoints if they were eligible?
- →What is the time complexity of your scan?
JuniorTheoryOccasionalWhy is x in a_list inside a loop the classic analyst performance mistake?
Why is x in a_list inside a loop the classic analyst performance mistake?
x in a_list is O(n) — it scans until it finds x, so in a loop over m items it is O(n * m). x in a_set or x in a_dict is average O(1), hashing the key to a bucket; converting the collection to a set once makes membership constant.
Common mistakes
- ✗Believing
inon a list is constant or logarithmic time - ✗Thinking the loop overhead, not the O(n) scan, is the cost
- ✗Leaving the lookup collection as a list inside a hot loop
Follow-up questions
- →What breaks if the ids you look up are unhashable?
- →How much memory does the set conversion cost you?
MiddleCodeOccasionalMaximum sum of non-adjacent houses
Maximum sum of non-adjacent houses
Dynamic programming with two rolling values. For each house, the best total up to it is max(skip_this = best_without_prev, take_this = best_before_prev + nums[i]). Track two rolling values and update them per house; the answer is the final running best. O(n) time, O(1) space.
Common mistakes
- ✗Assuming even-indexed houses are always optimal
- ✗Greedily taking the largest values regardless of structure
- ✗Forgetting the take-vs-skip choice at each house
Follow-up questions
- →How does the recurrence change if houses form a circle?
- →Why is the greedy largest-first approach wrong here?
MiddleCodeOccasionalLargest product of two numbers in a list
Largest product of two numbers in a list
The answer is max(top1 * top2, bottom1 * bottom2) — the two largest values OR the two smallest. The second candidate matters because two large-magnitude negatives multiply to a large positive. Find the top two and bottom two in one O(n) pass (or sorted for O(n log n)).
Common mistakes
- ✗Multiplying only the two largest, missing two negatives
- ✗Pairing the max with the min instead of two extremes
- ✗Dropping signs by taking absolute values
Follow-up questions
- →On which input does the two-largest-only approach fail?
- →How would you do it in one pass without sorting?
MiddlePerformanceOccasionalWhy is a per-value list.count tally far slower than one hash-based Counter pass?
Why is a per-value list.count tally far slower than one hash-based Counter pass?
data.count(v) is an O(n) scan; running it once per distinct value makes the tally O(n·u) — near O(n²) when values vary. Counter(data) (or a plain dict) hashes each element once in one O(n) pass, so the tally drops from 40s to under a second.
Common mistakes
- ✗Blaming loop or interpreter overhead, not the O(n) rescan
- ✗Calling
list.countorinonce per unique value - ✗Thinking Counter wins by a constant factor, not complexity class
Follow-up questions
- →When is a
list.countinside a loop actually acceptable? - →How does
Counterbehave on unhashable elements?
JuniorCodeRareCheck whether one of two positive integers divides the other evenly
Check whether one of two positive integers divides the other evenly
Test both directions of the modulo: return 1 if (b % a == 0 or a % b == 0) else 0. Either remainder being zero means one number divides the other. For a=1, b=10, 10 % 1 == 0 → 1; for a=15, b=6, neither 15 % 6 nor 6 % 15 is zero → 0.
Common mistakes
- ✗Checking only one division direction
- ✗Confusing divisibility with sum parity
- ✗Assuming only equal numbers divide evenly
Follow-up questions
- →Why is no zero-division guard needed for positive inputs?
- →How would the answer change if zero were allowed?
MiddleCodeRareGroup a list of strings into anagram buckets
Group a list of strings into anagram buckets
Bucket strings in a dict keyed by a canonical signature shared by all anagrams. The sorted-character tuple tuple(sorted(s)) works (or a 26-length letter-count tuple, which is O(n) per string instead of O(n log n)). Append each string to its key's list, then return list(d.values()).
Common mistakes
- ✗Grouping by length, which merges non-anagrams
- ✗Keying by first letter or ASCII sum, which collides
- ✗Using an O(n^2) pairwise comparison instead of a key
Follow-up questions
- →Why is a letter-count key faster than sorting per string?
- →How does the empty-string case behave with your key?
MiddleCodeRareCount 'honestly even' numbers from 1 to n
Count 'honestly even' numbers from 1 to n
Count numbers whose every digit is in {0,2,4,6,8}. A direct scan: sum(1 for k in range(1, n+1) if all(d in '02468' for d in str(k))). The trap is reading 'even number' as value % 2 == 0 — here the test is per-digit, not parity of the whole number.
Common mistakes
- ✗Counting plain even numbers (value % 2) instead of all-digits-even
- ✗Checking only the last digit
- ✗Using digit-sum parity instead of per-digit evenness
Follow-up questions
- →Why does 10 fail the honestly-even test?
- →How would a digit-DP approach scale this for huge n?