# Twice on the Wire

> A dropped ACK makes the reader say it again. Keep the signal, drop the echo.

Canonical URL: <https://datadriven.io/problems/twice-on-the-wire>

Domain: Python · Difficulty: easy · Seniority: junior

## Problem

A card reader retransmits its last status ping whenever an ACK is dropped, so the raw stream arrives with the same status repeated back to back. Collapse each run of consecutive identical pings into a single entry, keeping the surviving pings in their original order.

## Worked solution and explanation

### Why this problem exists in real interviews

This is run-length collapse wearing a payments costume, and the whole thing turns on one word: consecutive. The skill being probed is whether you compare each ping to its PREVIOUS neighbor, not to a bag of everything seen so far. Almost everyone hears 'remove the duplicates' and reaches for set(pings) or dict.fromkeys(pings). That erases every repeat globally, so the last 'online' in the first example, a genuine event that arrived after a 'degraded', silently disappears. Get it wrong and your collapsed stream is missing real authorizations and the count no longer matches reality.

---

### Break down the requirements

#### Step 1: Compare against the previous ping, not a seen-set

The rule is local: drop a ping only when it equals the one immediately before it. That is fundamentally different from global dedup. Keep the value you last emitted and skip while the next value matches it.

#### Step 2: Preserve order and non-adjacent repeats

A status that comes back later after something else in between is a brand-new event, so it must survive. The output is the sequence of run leaders in arrival order. Never sort, never hash the whole stream.

#### Step 3: Let empty and single-element inputs fall out for free

An empty stream returns an empty list and a one-element stream returns itself. A clean run-collapse handles both with no special branch, so resist adding guard clauses that only add noise.

---

### The solution

**groupby collapses consecutive runs in one pass**

```python
from itertools import groupby

def collapse_pings(pings):
    return [status for status, _ in groupby(pings)]
```

**Global dedup (wrong)**

list(dict.fromkeys(pings)) keeps first-seen order but removes EVERY later repeat. On ['online','online','degraded','online'] it returns ['online','degraded'], deleting the final real 'online'.

**Consecutive collapse (right)**

[k for k, _ in groupby(pings)] only merges adjacent equal values. Same input returns ['online','degraded','online'], preserving the genuine later event.

> **Complexity**
>
> O(n) time in a single pass, O(1) extra state beyond the output. groupby yields lazily and only compares neighbors, so this stays linear even on a multi-million-row settlement stream. No sorting, no hashing of the full list.

> **Interviewers Watch For**
>
> Whether you catch that 'duplicates' here means ADJACENT duplicates and reach for itertools.groupby (the specific Python trick), or at least a prev-value loop. Naming why set()/dict.fromkeys() is wrong out loud is the strongest signal of seniority on this one.

> **Common Pitfall**
>
> Using set(pings) or dict.fromkeys(pings) and calling it done. Both collapse non-adjacent repeats too, so a legitimate re-authorization that matches an earlier status vanishes. The visible test with a trailing 'online' exists precisely to catch this.

---

## Common follow-up questions

- What if pings were dicts like {'status': 'online', 'ts': 1234} and you collapse on the status field only? _(Tests groupby(pings, key=lambda p: p['status']) and deciding which record in the run to keep.)_
- How would you also return how many raw pings each surviving entry absorbed? _(Tests [(k, len(list(g))) for k, g in groupby(pings)] and awareness that the group iterator is consumed once.)_
- The stream is now infinite and arrives one ping at a time. How do you collapse it online? _(Tests a stateful generator that remembers only the last emitted value, no full-list buffering.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/twice-on-the-wire)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). 100% free data engineering interview prep. Live code execution against Postgres 16, Python 3.11, and Spark sandboxes. No paywall, no premium tier, no signup gate.