Candidate: Aidan McDowell | Role: Ground Software Engineer II — Long Beach, CA | Interviewer: Anh Thai (Ground Software Engineer) | Format: 30 min · Microsoft Teams · ~20–25 min coding
Click any blue button to jump to that section. Every section has a [ ↩ Back ] button to return here.
Step 1 — Identify the archetype in 5 seconds:
Step 2 — Ask clarifying questions while reading: 2 · Clarifying Questions
if not packets: return {}.get("key") — never ["key"]if count > 0:["key"] for .get("key")if val is not None:if count > 0:if not isinstance(packet, dict): continuedict.get() vs dict[]: packet["voltage"] crashes with KeyError if key is absent. packet.get("voltage") returns None safely.None Math Trap: You cannot do > or + on None. Always check if val is not None: before any math.if count > 0:.None, or with missing keys like subsystem or voltage?"Keywords: voltage, temperature, status, corrupt, missing, average, max, threshold, flag
The 5-Step Mental Model:
{}.get() on every keystatus != "OK" or voltage is None or not namesum, count, max per subsystemavg and flagged = avg > 32.0def process_bus_telemetry(packets):
# SAY: "First thing — guard the empty case up front"
if not packets:
return {}
subsystems = {} # SAY: "Dict to accumulate per-subsystem running stats"
for packet in packets:
if not isinstance(packet, dict): # SAY: "Skip corrupt non-dict entries from the downlink"
continue
# SAY: "Using .get() — never bracket access on untrusted data"
name = packet.get("subsystem")
voltage = packet.get("voltage")
status = packet.get("status")
# SAY: "Triple filter — skip ERROR status, None voltages, missing names"
if status != "OK" or voltage is None or not name:
continue
# SAY: "Initialize new subsystem with first voltage as seed max"
if name not in subsystems:
subsystems[name] = {"sum": 0, "count": 0, "max": voltage}
# SAY: "Running accumulation — O(1) per packet"
subsystems[name]["sum"] += voltage
subsystems[name]["count"] += 1
if voltage > subsystems[name]["max"]:
subsystems[name]["max"] = voltage
# SAY: "Build output dict — count > 0 is guaranteed since we only stored valid packets"
result = {}
for name, data in subsystems.items():
avg = data["sum"] / data["count"]
result[name] = {
"max" : data["max"],
"avg" : avg,
"count" : data["count"],
"flagged": avg > 32.0 # SAY: "Flag subsystems above 32V threshold"
}
return result
| Edge Case | Guard Used |
|---|---|
| Empty list | if not packets: return {} |
| None voltage | voltage is None → continue |
| Missing key | packet.get("subsystem") |
| Corrupt non-dict | isinstance(packet, dict) |
| ZeroDivisionError | count guaranteed > 0 since only valid packets enter accumulator |
Keywords: passes, schedules, contact windows, overlap, intervals, merge, antenna, LOS
The 3-Step Mental Model:
[]current_start <= prev_end → merge (extend end); else → append new blockdef merge_passes(intervals):
# Guard: empty input
if not intervals:
return []
# Step 1: Sort chronologically by start time
intervals.sort(key=lambda x: x[0])
merged = [intervals[0][:] ] # copy of first window
for start, end in intervals[1:]:
prev_start, prev_end = merged[-1]
if start <= prev_end: # overlapping — extend
merged[-1][1] = max(prev_end, end)
else: # gap — new block
merged.append([start, end])
return merged
| Input | Output |
|---|---|
[[10,14],[12,18],[20,25]] | [[10,18],[20,25]] |
[] | [] |
[[1,5],[6,10]] | [[1,5],[6,10]] — not overlapping |
[[20,25],[1,3],[2,7],[15,22]] (unsorted) | [[1,7],[15,25]] |
Key insight: Adjacent intervals like [1,5] and [6,10] are not overlapping — overlap requires start <= prev_end (strictly less than or equal).
Keywords: throttle, max commands, rate limit, sliding window, transceiver, uplink
The 3-Step Mental Model:
__init__: store max_cmds, window_sec, and a deque of timestampsallow_command(t): purge timestamps older than t - window_seclen(deque) < max_cmds: append t → return True. Else False.from collections import deque
class CommandLimiter:
def __init__(self, max_cmds: int, window_sec: int):
self.max_cmds = max_cmds
self.window_sec = window_sec
self.timestamps = deque() # O(1) popleft
def allow_command(self, timestamp: float) -> bool:
cutoff = timestamp - self.window_sec
# Step 1: Purge expired timestamps from the front
while self.timestamps and self.timestamps[0] <= cutoff:
self.timestamps.popleft()
# Step 2: Check capacity
if len(self.timestamps) < self.max_cmds:
self.timestamps.append(timestamp)
return True
return False
| Call | Result | Why |
|---|---|---|
allow(1.0) | ✅ True | 1st of 2 allowed |
allow(2.0) | ✅ True | 2nd of 2 allowed |
allow(3.0) | ❌ False | 2 commands in [1.0–6.0] |
allow(6.5) | ✅ True | 1.0 expired (6.5–5=1.5) |
Why deque? popleft() is O(1). A list's pop(0) is O(N). At 100k commands/sec that matters.
Keywords: flight modes, stage separation, transitions, ABORT, illegal jump, state, sequence
The 4-Step Mental Model:
{"valid": False, "error": "No events"}"PRE_LAUNCH"VALID_TRANSITIONS = {
"PRE_LAUNCH" : ["BOOST"],
"BOOST" : ["STAGE_SEP"],
"STAGE_SEP" : ["COAST"],
"COAST" : ["PAYLOAD_DEPLOY"],
"PAYLOAD_DEPLOY" : ["MISSION_COMPLETE"],
"MISSION_COMPLETE": []
}
def validate_sequence(events):
if not events:
return {"valid": False, "error": "No events provided"}
if events[0] != "PRE_LAUNCH":
return {"valid": False,
"error": f"Must start with PRE_LAUNCH, got {events[0]}"}
for i in range(1, len(events)):
current = events[i - 1]
nxt = events[i]
allowed = VALID_TRANSITIONS.get(current, []) # safe: unknown state → []
if nxt not in allowed:
return {"valid": False,
"error": f"Invalid transition from {current} to {nxt} at index {i}"}
return {"valid": True, "final_state": events[-1]}
| Input | Output |
|---|---|
["PRE_LAUNCH","BOOST","STAGE_SEP"] | ✅ valid, final_state STAGE_SEP |
["PRE_LAUNCH","PAYLOAD_DEPLOY"] | ❌ Invalid at index 1 |
["BOOST","STAGE_SEP"] | ❌ Must start with PRE_LAUNCH |
[] | ❌ No events provided |
["PRE_LAUNCH","BOOST","WARP_DRIVE"] | ❌ WARP_DRIVE not in allowed |
Keywords: seq_num, sequence counter, monotonic, dropped frames, packet loss, RF drop, gap, loss_rate_pct, frame_gap
The 4-Step Mental Model:
{received:0, dropped:0, loss_rate_pct:0.0, missing_seqs:[]}.get() + isinstance check, sort them(last_seq - first_seq) + 1 — this is expected count, not receivedset(), loop range(first, last+1) — anything missing goes in missing[]def detect_packet_drops(packets):
# SAY: "Guard empty input first — can't index an empty list"
if not packets:
return {"received": 0, "dropped": 0, "loss_rate_pct": 0.0, "missing_seqs": []}
# SAY: "Filter with .get() — skip None seq_nums and non-dict packets"
valid_seqs = sorted([
p.get("seq_num")
for p in packets
if isinstance(p, dict) and p.get("seq_num") is not None
])
if not valid_seqs:
return {"received": 0, "dropped": 0, "loss_rate_pct": 0.0, "missing_seqs": []}
first_seq = valid_seqs[0]
last_seq = valid_seqs[-1]
expected_count = (last_seq - first_seq) + 1 # SAY: "Span of expected IDs"
received_set = set(valid_seqs) # SAY: "O(1) membership check"
missing = []
for num in range(first_seq, last_seq + 1):
if num not in received_set:
missing.append(num)
dropped_count = len(missing)
# SAY: "Loss rate over EXPECTED span — not over received"
loss_pct = round((dropped_count / expected_count) * 100.0, 2)
return {
"received" : len(valid_seqs),
"dropped" : dropped_count,
"loss_rate_pct": loss_pct,
"missing_seqs" : missing
}
| Input (seq_nums) | received | dropped | missing_seqs | loss_rate_pct |
|---|---|---|---|---|
[101,102,104,105] | 4 | 1 | [103] | 20.0% |
[10,11,12] | 3 | 0 | [] | 0.0% |
[] | 0 | 0 | [] | 0.0% |
[5, None, 7] (None skipped) | 2 | 1 | [6] | 50.0% |
Key trap: Loss rate denominator = expected span (last − first + 1), not received count. 4 received out of span 5 → 20% loss, not 25%.
Curveball: "The seq_num wraps at 65535 (16-bit). How does your detector handle [65534, 65535, 0, 1]?" — Answer: naively it would report 65534 false drops. Fix: detect rollover with if last_seq < first_seq: last_seq += 65536.
Keywords: OOL, Out-of-Limits, yellow_low/high, red_low/high, WARNING, CRITICAL, alarm_severity, abort threshold, limit band
The 4-Step Mental Model:
if not telemetry_points or not limit_table: return []isinstance check, .get() for sensor/value/ts, skip if unknown sensor or val is Noneelif Yellow (WARNING). Never check yellow before red.severity is not Nonedef evaluate_telemetry_limits(telemetry_points, limit_table):
if not telemetry_points or not limit_table:
return []
alarms = []
for point in telemetry_points:
if not isinstance(point, dict):
continue
sensor = point.get("sensor")
val = point.get("value")
ts = point.get("timestamp")
if sensor not in limit_table or val is None:
continue
limits = limit_table[sensor]
severity = None
# SAY: "Red check FIRST — critical violations take precedence"
if val < limits.get("red_low", float('-inf')) or \
val > limits.get("red_high", float('inf')):
severity = "CRITICAL"
elif val < limits.get("yellow_low", float('-inf')) or \
val > limits.get("yellow_high", float('inf')):
severity = "WARNING"
if severity:
alarms.append({
"sensor" : sensor,
"value" : val,
"severity": severity,
"timestamp": ts
})
return alarms
| pressure_psi value | Yellow [2800–3200] | Red [2500–3500] | Alarm |
|---|---|---|---|
| 3000 | ✅ nominal | ✅ nominal | None |
| 3300 | ❌ above 3200 | ✅ nominal | WARNING |
| 3600 | ❌ | ❌ above 3500 | CRITICAL |
| 2400 | ❌ | ❌ below 2500 | CRITICAL |
| None | — | — | Skipped |
Key trap: Use float('-inf') / float('inf') as .get() defaults so missing limit keys never raise a TypeError.
Curveball: "Transient spikes — alert only after 3 consecutive OOL readings." — Answer: add a consecutive = {} dict tracking per-sensor streak; only emit alarm when consecutive[sensor] >= 3.
Keywords: ACK, command acknowledgment, uplink, telemetry_ack, round_trip_latency, command_timeout, CONFIRMED, REJECTED, TIMEOUT
The 3-Step Mental Model:
if not commands: return {}. Build ACK index: ack_map = {a["cmd_id"]: a for a in (acks or [])}.get() — skip missing cmd_id or sent_timecmd_id not in ack_map → "TIMEOUT". Else check status == "REJECTED" first, then timing: ack_time - sent_time <= timeout_secdef verify_command_acks(commands, acks):
if not commands:
return {}
# SAY: "Index ACKs by cmd_id for O(1) lookup — never loop inside a loop"
ack_map = {}
for a in (acks or []): # (acks or []) handles None acks safely
if isinstance(a, dict) and "cmd_id" in a:
ack_map[a["cmd_id"]] = a
results = {}
for cmd in commands:
if not isinstance(cmd, dict):
continue
cid = cmd.get("cmd_id")
sent = cmd.get("sent_time")
timeout = cmd.get("timeout_sec", 5.0)
if not cid or sent is None:
continue
if cid not in ack_map:
results[cid] = "TIMEOUT" # No ACK received at all
continue
ack = ack_map[cid]
ack_time = ack.get("ack_time", float('inf'))
ack_status = ack.get("status")
# SAY: "REJECTED check before timing — status takes priority"
if ack_status == "REJECTED":
results[cid] = "REJECTED"
elif ack_time - sent <= timeout:
results[cid] = "CONFIRMED"
else:
results[cid] = "TIMEOUT" # ACK arrived, but too late
return results
| cmd_id | sent_time | timeout_sec | ack_time | ack_status | Result |
|---|---|---|---|---|---|
| CMD_001 | 100.0 | 5.0 | 103.0 | OK | ✅ CONFIRMED |
| CMD_002 | 200.0 | 3.0 | 210.0 | OK | ⏱ TIMEOUT (10s > 3s) |
| CMD_003 | 300.0 | 5.0 | 302.0 | REJECTED | ❌ REJECTED |
| CMD_004 | 400.0 | 5.0 | — | — | ⏱ TIMEOUT (no ACK) |
Key trap: Use (acks or []) — if the caller passes acks=None, iterating over None raises a TypeError. The or [] idiom handles it silently.
Curveball: "Spacecraft sends two ACKs per command: Accepted + Executed. A REJECTED on Executed means it accepted but failed mid-execution." — Answer: store a list of ACKs per cmd_id; take the last ACK as the definitive status.
| Error | Cause | Fix |
|---|---|---|
KeyError |
Using packet["key"] when key is absent |
packet.get("key") |
TypeError: '>' not supported on None |
Comparing or adding None |
if val is not None: before math |
ZeroDivisionError |
Dividing by count when no valid packets | if count > 0: avg = total / count |
AttributeError |
Packet is a string or None, not a dict |
if not isinstance(packet, dict): continue |
.get(): "Using .get() so missing telemetry keys don't throw an unhandled KeyError."