skip to content

Why does a one-pass seen-set beat sorting for finding the first repeated check-in badge?

level: middleimportance: should knowfreq 58%

answer

  1. the log arrives in an order that matters
  2. sorting rearranges what you were asked about
  3. one membership test per entry
  4. test before you insert, never after
  5. check the empty and all-identical logs

basics

~20 s

Sorting answers a different question: it destroys arrival order, so "first repeat" stops being defined, and it costs O(n log n). A one-pass seen-set tests membership before each insert, reports the earliest repeated badge in O(n) expected time, and stops the moment it finds one.

solid answer

~50 s

At a conference check-in desk the log arrives in scan order, and the requirement is the first badge scanned twice — a statement about that order. Sorting the log and comparing neighbours does find duplicates, but it rearranges the very thing being asked about, so the duplicate it surfaces is the smallest one, not the earliest; it also costs O(n log n) and touches every entry even when the repeat happens on scan two. The seen-set walks the log once, and for each badge asks the set whether it is already present *before* inserting it: a hit is the answer, and the loop returns immediately. That is O(n) expected time and O(n) space, with early exit. Watch the edges: an empty log returns nothing because the body never runs, and an all-identical log returns on the second entry after exactly one insert.

code

pseudocode · 8 lines
pseudocode
seen = empty set
for i in 0..length(scans)-1
    badge = scans[i]
    if seen contains badge
        return badge          // earliest badge whose second scan we reached
    add badge to seen
...
return none                   // every badge distinct: no repeat exists

go deeper

for a junior

Know the one-pass shape: for each item, ask the set whether it is present, then add it. Be able to say it is expected linear time and uses extra memory proportional to the distinct items seen.

for a middle

Explain the invariant that the set holds only earlier entries, why the membership test must precede the insertion, and why sorting answers a weaker question at a higher asymptotic cost.

for a senior

Demonstrate edge-case discipline unprompted — empty input, all-identical input, all-distinct input — and be explicit about the memory you traded for the linear time and when that trade is not available.

for a principal

Own the framing that the requirement's wording determines the structure: presence, first position, or per-key counts are three different asks with three different best answers, and vague requirements are where these implementations quietly go wrong.

## The requirement is about order A conference check-in desk records badge identifiers in the order they are scanned. Staff want the first badge that was scanned twice — the earliest point at which someone re-entered or a badge was cloned. Notice that this requirement mentions *arrival order*: the answer is defined by the position of the second scan in the log. ## Why sorting answers a different question Sorting the log puts equal identifiers next to each other, and a linear neighbour comparison then finds duplicates reliably. Equal keys really do become adjacent, so the "sorting can miss non-adjacent duplicates" worry is unfounded. The problem is subtler and worse: sorting **discards the ordering the requirement is stated in**. After sorting, the first duplicate you meet is the smallest duplicated identifier, which has nothing to do with which badge was re-scanned first. To keep the answer you would have to sort pairs of (identifier, original position) and then take the minimum second-occurrence position across all duplicate groups — more work and more code for a worse result. The cost profile is worse too. A comparison sort is Omega(n log n) — that lower bound is about comparison sorts specifically, and non-comparison sorts such as counting or radix escape it only when the key domain permits, which arbitrary badge identifiers may not. Sorting also has no early exit: it must order the whole log even if the repeat happens on the second scan. And it either mutates the caller's log or costs an O(n) copy. ## What the seen-set does instead One pass, one invariant: **the set contains exactly the badges from entries strictly before the current one.** For each entry, test membership first; a hit means this badge already appeared earlier in scan order, and because we are walking forward, this is the earliest second-scan we could have reached, so return immediately. Otherwise add the badge and continue. Expected O(1) per entry gives O(n) expected time overall, with worst-case O(n) per lookup if hashing degrades badly. Extra space is O(n) — the set may end up holding every distinct badge — and that memory is the price paid for the speed. Order inside the loop is again the whole correctness argument. Insert before testing and every badge is instantly "already present", so the first entry is reported as a duplicate. Test-then-insert is what makes the invariant true. ## The edge inputs an interviewer probes - **Empty log.** The loop body never executes; the function returns "no repeat". Candidates who claim it returns the first badge have not traced the loop. - **Every badge distinct.** n membership tests all miss, n insertions happen, and the function returns "no repeat" after touching the whole log. This is the worst case for both time and memory. - **Every badge identical.** The first entry misses and is inserted; the second entry hits and returns. One insert, two iterations — the best case, and a good demonstration of the early exit sorting cannot offer. - **A single entry.** One miss, one insert, no repeat. ## Set or map here? A set is correct precisely because the requested output is the badge itself, which you already hold in hand when the hit occurs. Change the requirement to "report the position of both scans" and it becomes a map from badge to its first position, because the earlier position is now data that must travel with the key. Change it to "how many times was each badge scanned?" and it becomes a map to a count, and the early exit disappears — you now have to read the whole log. Let the required output pick the structure, and notice that each of those three requirements has a different best structure even though all three are "about duplicates". ## A related mis-statement worth avoiding After a full pass, the set's size is the number of **distinct** badges, not the number of duplicates. The count of duplicate scans is the log length minus that size, and even that tells you nothing about which badge repeated first. Reporting the set's size as a duplicate count is a common slip. ## What to say when asked Lead with the observation that the question is stated in arrival order, so any approach that reorders the log answers a different question. Then give the loop and its invariant, the O(n) expected time versus O(n log n), the early exit, the O(n) memory you traded for it, and the empty and all-identical edge cases.

  • What does the loop report for an empty scan log, and for a log where every badge is identical?
    An empty log reports no repeat: the body never runs, so no membership test happens. A log of identical badges returns that badge on the second entry — the first entry misses and is inserted, the second hits — which is also the best case, demonstrating the early exit a sort-based approach cannot provide.
  • The requirement changes to "report the positions of both scans of that badge". What changes?
    The structure becomes a map from badge to the position of its first scan, because the earlier position is now data that must travel with the key. The loop is otherwise identical: on a hit, return the stored earlier position together with the current index. Time and space stay O(n) expected.
  • When would you accept the sort-based approach anyway?
    When memory is the binding constraint and any duplicate will do — sorting in place needs no auxiliary structure proportional to the distinct-badge count. Also when the log is already sorted or nearly so for other reasons, or when the identifiers are so large that hashing them repeatedly dominates. You are then explicitly answering the weaker question, and should say so.

saying these in an interview costs you the question

  • Says sorting and comparing neighbours is exactly equivalent
  • Claims sorting can leave equal identifiers non-adjacent
  • Inserts before testing, so the first entry looks like a duplicate
  • Thinks an empty log makes the loop report a repeat
  • Reports the set's final size as the number of duplicates

context