Skip to main content
CodeOath
← All posts

Python105 min total · 18 parts

Python Fundamentals for Interviews: Data Structures, Comprehensions, and Gotchas

Part 18 of 18 · ~3 min

Common Mistakes Worth Remembering

Every one of these is a bug this watcher actually had, at some point in this reference:

  • offenders=[] sitting in a function signature, or flagged = [] sitting on a class — one shared object slowly absorbing state from every unrelated caller instead of starting fresh each time, because it was only ever built once.
  • Checking status is 401 instead of status == 401 — passes every test built around small, commonly-cached numbers, then fails without a single error the day the value in question falls outside that range.
  • Pulling two different answers out of one generator with two separate sum() calls, and not noticing the second answer is zero rather than merely wrong.
  • Writing [lambda: i for i in range(3)]-shaped code and expecting three different captured values — every one of those closures shares the same i, so all three see whatever i finished at, not whatever it was when each closure was written.
  • Writing a bare except: to "handle everything," and discovering it also intercepts the Ctrl+C you were using to stop the program.
  • Adding __eq__ to a class for one unrelated reason and later being unable to explain why a completely different part of the codebase can no longer put instances of it in a set.
  • Decorating an async def with a decorator that was written for ordinary functions — it looks like it works, because calling a coroutine function never itself raises; the decorator's own logic never runs at all, and the real failure only surfaces once something finally awaits the result.
  • Scanning a list with in inside a loop where a set would turn that same check from O(n) into O(1).
  • Growing a string with += inside a loop instead of collecting the pieces and calling str.join once, turning what should be O(n) work into O(n²).

The same bugs, somewhere else entirely

One risk of building a whole reference around a single running program: it's possible to learn "the log watcher's bugs" instead of "the actual bugs." So here are two of them again, with no IPs, no logs, and no rate limits anywhere in sight — a quiz-grading tool:

def grade_quiz(answers, wrong=[]):          # same shape as flag_offenders, new costume
    for question, given in answers.items():
        if given != ANSWER_KEY[question]:
            wrong.append(question)
    return wrong

Grade one student, and the next student's "wrong answers" list starts out already containing the first student's mistakes — identical mechanism, nothing to do with servers or requests.

pass_thresholds = []
for min_score in [50, 75, 90]:              # pass / merit / distinction
    pass_thresholds.append(lambda score: score >= min_score)

[check(80) for check in pass_thresholds]    # [True, True, True] — every check uses 90, the last value

If both of those read as immediately familiar rather than as new bugs to figure out, the underlying mechanism transferred — which was the entire point of building one program instead of nine unrelated ones.

The code lab is the place to actually type these out rather than just read them — start with the mutable default and the loop-closure trap, since those two are the ones most likely to show up disguised as someone else's bug in an interview. And once a request has made it past this watcher, Django Fundamentals picks up the other half of the story: what actually handles it on the way in.