Mission: practical Python • robotics runtime • July 27, 2026

Get the servo velocity right.

A seven-day, evidence-backed sprint for Physical Intelligence’s one-hour coding interview. The recruiter gave an unusually precise clue: you will write a Python script to measure servo velocity. This plan turns that sentence into a rehearsed engineering performance.

7 daysMon Jul 20 → Sun Jul 26
26–32 hhigh-value focused work
4 mocksincluding two full 60-minute runs
Jul 27target interview day

Watch the servo-velocity crash course

A visual, narrated pass through the engineering decisions you need to make automatic.

2 min 9 sec9 chaptersPython + testingTiming, units, wraparoundState + noise
01 · ContractClarify API, timestamps, units, wrap semantics, and output.
02 · BaselineTurn two position samples into a signed velocity estimate.
03 · HardenHandle time integrity, state, noise, and invalid readings.
04 · ProveBuild clean Python with injected dependencies and focused tests.
05 · PracticeRotate through 40 variations and maintain an error log.
06 · PerformUse the hour deliberately: clarify, implement, test, harden, explain.

The 1920×1080 master is hosted as a HeyGen video asset for sign-in-free playback. A local MP4 copy is saved alongside this HTML source.

1. Ground truth, uncertainty, and the bet

Do not waste this week preparing for the wrong interview.

Direct evidence

“It will be a very practical coding question — we don’t do any leetcode… you will write a Python script to measure servo velocity.”

This email is the highest-confidence source in the entire plan. Treat every other prediction as secondary.

Verified

  • One-hour technical coding interview.
  • Python script.
  • Measure servo velocity.
  • Practical engineering, explicitly not LeetCode.
  • Physical Intelligence’s open-source docs use joint angles in radians and, depending on platform, 15, 20, or 50 Hz control; DROID uses seven joint-velocity dimensions.

Not verified

  • Whether input is a live servo API, a simulator, iterable samples, or a CSV/log.
  • Whether third-party libraries are allowed.
  • Whether “servo” means a single actuator or a multi-joint robot arm.
  • Whether they expect instantaneous, average, filtered, angular, or linear velocity.
  • Exact API names, units, and output format.
Informed inference

The most likely core is: repeatedly read position and time, calculate Δposition / Δtime, handle real-world sampling problems, and expose or print a useful estimate. The evaluation is likely less about exotic math than assumptions, interface design, correctness, tests, noise, timing, and communication.

Public interview-question search result

I found only one public Glassdoor report, for a Lead Robot Test Pilot, describing timed physical tasks (shirt folding and cup stacking) and a general robotics-experience question. It is a different role and provides no coding prompt. No credible public source surfaced this servo-velocity interview question. Therefore, this plan does not pretend to reproduce a leaked prompt.

The preparation allocation

ShareAreaWhy
45%Implementing and testing velocity measurementThe explicit prompt clue.
20%Python fluency under time pressureClean functions/classes, iterators, dataclasses, typing, CLI parsing, CSV, tests.
15%Real-world robustnessNon-monotonic timestamps, noise, wraparound, missing samples, units, multi-axis data.
10%Verbal problem solvingClarifying, narrating tradeoffs, responding to interviewer changes.
10%PI-specific context + rest/logisticsSignal motivation and protect interview-day performance.

2. What “excellent” looks like

Your north star and stopping rules.

The minimum viable solution

  1. Confirm the source of positions and timestamps, units, output semantics, and whether velocity is signed.
  2. Read two valid samples.
  3. Compute velocity = (position_now - position_prev) / (time_now - time_prev).
  4. Reject or explicitly handle dt <= 0.
  5. Return a testable value rather than burying logic inside I/O.
  6. Verify with a hand-calculated constant-velocity example.

The strong solution

Correct contract

States assumptions; consistent units; signed direction; clear definition of instantaneous vs windowed velocity.

Separation

Hardware/sample acquisition, estimation, and reporting are separate. Synthetic data can test the estimator.

Robust time

Uses supplied device timestamps or a monotonic clock; never wall-clock time for intervals.

Edge cases

First sample, duplicate/out-of-order time, malformed values, angular wrap, and empty/short streams.

Noise awareness

Explains why differentiation amplifies noise; adds filtering only after a correct baseline.

Evidence

Small tests: stationary, constant velocity, irregular sampling, invalid timestamps, multi-axis.

Do not overengineer early. A candidate who spends 35 minutes designing a Kalman filter before producing Δx/Δt is in danger. Get a correct thin slice, test it, then add robustness in descending value order.

Readiness gates by Sunday night

  • You can implement the baseline from a blank file in 8 minutes.
  • You can add streaming, validation, and four tests in 25 minutes total.
  • You can explain forward vs central difference, filtering tradeoffs, and monotonic time without notes.
  • You can complete the full mock with a working solution by minute 40 and spend the remainder testing/refining.
  • You score ≥ 16/20 twice using the rubric below, with no zero in correctness or communication.

3. Seven-day execution calendar

Times are adjustable; preserve order and deliverables. Work in 50/10 or 75/15 focus cycles.

Schedule principle: every study block ends with an artifact—code, tests, a spoken recording, or a written error log. Passive reading is capped at 25% of the week.
MON • JUL 20

Baseline + problem decomposition

Build the simplest correct estimator and find your Python weak spots.

4–5 h
Closed-book diagnostic. From a blank editor, write a function taking timestamped positions and returning velocities. Add any tests you naturally think of. Stop at 45 minutes—do not polish.
Postmortem. Mark every hesitation: Python syntax, API ambiguity, math, test design, edge cases, typing, debugging. This becomes your personal gap list.
Core implementation. Implement scalar batch and streaming estimators using only the standard library. Cover first sample and dt <= 0.
Python refresh. Practice dataclasses, type hints, iterators/generators, exceptions, argparse, csv, math.isfinite, and collections.deque.
Tests. Stationary, constant positive/negative velocity, irregular timestamps, duplicate timestamp, one sample.

Exit artifact: velocity_v1.py + tests + one-page gap log. Baseline works from a blank file.

TUE • JUL 21

Timing, units, angular motion

Make the baseline physically and numerically honest.

4–4.5 h
Timing lab. Compare device timestamps, time.time(), time.monotonic(), monotonic_ns(), and perf_counter(). Write down which you would use and why.
Angular wraparound. Implement shortest signed angular difference for a period of 2π or 360°. Test 179° → −179° and the reverse.
Multi-axis extension. Accept a sequence of joint positions; validate dimensional consistency; return per-joint signed velocities plus optional speed norm.
I/O practice. Read timestamp,joint_0,... CSV; stream output; report malformed rows with useful line numbers. Keep estimation pure.
Explain aloud. Units (rad/s vs deg/s), velocity vs speed, sampling rate vs measured dt, and why wall time can jump.

Exit artifact: velocity_v2.py, CSV adapter, wraparound and multi-axis tests, 3-minute spoken explanation.

WED • JUL 22

Noise, filtering, numerical differentiation

Know enough signal processing to make sound tradeoffs—without disappearing into theory.

4–5 h
Derivatives. Derive and code forward/backward difference and central difference. Understand endpoint behavior and irregular sample spacing.
Synthetic experiment. Generate constant and sinusoidal motion with timestamp jitter and position noise. Compare raw finite differences with ground truth; calculate MAE/RMSE.
Filtering. Implement a rolling-window slope via least-squares and a simple exponential moving average. Compare noise reduction, lag, startup, and memory.
Optional library literacy. Understand numpy.gradient and Savitzky–Golay conceptually, but be able to solve without NumPy/SciPy.
Decision memo. “Which estimator would I ship for feedback, telemetry, offline analysis, and safety limit detection?” One paragraph each.

Exit artifact: noisy-data benchmark, two filtered estimators, plot or metrics table, tradeoff memo.

THU • JUL 23

Production shape + mock #1

Turn math into a maintainable little system.

4–4.5 h
Design exercise. Define a Sample, a sensor/servo protocol, a velocity estimator, and a reporter. Add dependency injection so no hardware is required for tests.
Runtime hazards. Discuss polling rate, blocking reads, backpressure, thread safety (only if needed), clock domain mismatch, dropped samples, and shutdown via KeyboardInterrupt.
Mock #1. Use Prompt A below. Screen-record or voice-record. No notes or web. Ask clarifying questions aloud before coding.
Grade and redo. Score with the 20-point rubric. Fix only after writing down what caused each miss. Reimplement the weakest part from blank.
PI context. Read openpi overview and normalization/action-space docs; connect the task to joint actions, action frequencies, and robot runtime reliability.

Exit artifact: production-shaped estimator, mock recording, rubric score, top-three correction list.

FRI • JUL 24

Requirement changes + full mock #2

Practice being interrupted and adapting cleanly.

4–4.5 h
Rapid drills. Four 10-minute exercises: parse CSV; write a generator; add validation; write five table-driven tests.
Full mock #2. Prompt B. At minute 25 introduce multi-joint support; at minute 40 introduce noisy measurements. Narrate reprioritization.
Adversarial tests. NaN/inf, duplicate and decreasing timestamps, changing dimensions, huge gaps, tiny dt, stationary noise, wrap boundary.
Debugging practice. Seed five defects into yesterday’s code. Diagnose using failing tests and small prints/debugger. Explain hypotheses before changing code.
Story prep. Prepare concise examples of a runtime/reliability bug, ambiguous hardware interface, high-stakes tradeoff, and cross-functional debugging.

Exit artifact: ≥15/20 mock, edge-case suite, 4 concise behavioral stories.

SAT • JUL 25

Interview simulation day

Two realistic attempts, one deep correction loop.

4–5 h
Full mock #3. Randomly select Prompt C or D. Use the likely 60-minute structure. No autocomplete if the real environment is unknown.
Review. Watch at 1.5×. Track silence, premature coding, untested assumptions, meandering, and whether a working baseline appeared by minute 25.
Targeted repair. Drill the two lowest rubric categories. Do not broadly review topics you already own.
Full mock #4. Fresh prompt, ideally with another person acting as interviewer. Ask them to interrupt and change one requirement.
Final compare. You need ≥16/20 and a complete baseline by minute 30. If not, schedule one 45-minute corrective block Sunday morning.

Exit artifact: two rubric sheets; one ≥16/20; final “default approach” committed to memory—not memorized code.

SUN • JUL 26

Taper, logistics, confidence

Consolidate; do not exhaust yourself.

2.5–3 h
Final corrective mock only if needed. Otherwise do a relaxed blank-file baseline + three tests and stop.
One-page review. Rewrite from memory: formula, assumptions, clock choice, edge cases, filters, test matrix, interview opening, company motivation.
Environment. Confirm time zone and meeting link; Python version; editor; terminal; screen sharing; charger; headphones; reliable internet; quiet room; backup hotspot. If interviewing from India, verify the calendar conversion twice.
Warm-up package. Prepare one 10-minute exercise for Monday: simple moving average + two tests. Nothing novel.
Hard stop. Finish by early evening, eat normally, take a walk, and protect 8 hours of sleep. The last 5% of content is not worth degraded working memory.

Exit artifact: clean desk, verified logistics, one-page cheat sheet, rested brain.

MON • JUL 27

Interview-day protocol

Calm, warmed up, and early.

60–90 min prep
Wake/move/eat. No frantic studying. Confirm meeting time and connectivity.
10-minute warm-up; run two tests. Review only the one-page sheet. Close unrelated apps and notifications.
Open editor/terminal, water, meeting link, charger. Use restroom. Two minutes of slow breathing.
Join early. Your objective is not to know a hidden answer; it is to collaborate visibly toward a correct, tested program.

4. Servo-velocity technical primer

The compact body of knowledge most likely to matter.

Definitions first

Velocity

Rate of change of position, including direction. Angular velocity is commonly rad/s or deg/s; linear velocity is m/s.

Speed

Magnitude of velocity. For a scalar signed joint velocity, speed is abs(v). For multi-axis velocity, clarify whether they want per-axis values or a norm.

v ≈ Δq / Δt = (q₂ − q₁) / (t₂ − t₁)

For samples (tᵢ, qᵢ), the simplest online estimate at sample i is the backward difference. It is causal and low-memory, but differentiation magnifies position noise.

Estimator choices

MethodStrengthWeaknessBest interview use
Two-point backward differenceSimple, online, minimal latencyNoisyAlways build this baseline first
Central differenceUsually more accurate at interior pointsNeeds a future sample; endpoint handlingOffline arrays/logs
Window endpointsReduces some noiseLag; ignores interior structureQuick windowed improvement
Least-squares slopeUses every point; handles irregular timesWindow lag and more codeStrong practical extension
EMA of raw velocityConstant memory, tunable smoothingPhase lag; parameter depends on timingStreaming telemetry
Savitzky–Golay derivativeSmooth derivative, preserves shapesDependency, parameter constraints, typically offline/windowedDiscuss if libraries/offline analysis allowed

Why timestamps are not optional

  • Never assume a nominal 50 Hz loop means every interval is exactly 20 ms. Scheduling and I/O jitter exist.
  • Prefer timestamps generated by the device/data source if they share one stable clock domain.
  • If you timestamp locally, use a monotonic clock for intervals. Python documents time.monotonic() as unable to go backward and unaffected by system-clock updates.
  • Nanosecond integer variants avoid float precision loss, but remember to convert ns to seconds before reporting rad/s.
  • If position and timestamp come from separate reads, discuss temporal skew; an atomic sample API is better.

Angular wraparound

If an encoder reports angles in a wrapped interval, naïve subtraction may invent a huge jump. The shortest signed difference for period P is:

def shortest_delta(current: float, previous: float, period: float) -> float:
    return (current - previous + period / 2) % period - period / 2

But do not apply wrap correction blindly: some joint encoders report continuous/unwrapped angle, and some joints physically cannot cross the wrap boundary. Ask.

Least-squares slope for noisy, irregular samples

Fit q = a + vt over a small recent window. The slope is:

v = Σ(tᵢ − t̄)(qᵢ − q̄) / Σ(tᵢ − t̄)²

Subtracting the mean also improves numerical conditioning when timestamps are large absolute numbers. Reject a zero denominator. A time-based window can behave more consistently than a fixed number of samples when rates vary.

Edge-case decision table

ConditionSafe defaultSay aloud
First sampleReturn None; cache itThere is not yet an interval.
dt == 0Reject/skip explicitlyDivision is undefined; duplicate timestamp may indicate bad data.
dt < 0Reject, do not silently sort a live streamOut-of-order data or mixed clock domains.
NaN/∞Validate with math.isfiniteInvalid samples should not poison estimator state.
Tiny dtOptionally enforce minimum intervalQuantization/noise creates huge estimates.
Large gapReturn average over gap or reset, per contractIt may no longer represent instantaneous behavior.
Changing joint countRaise a descriptive errorDimensional consistency is part of the API.
Position noiseBaseline first; optional window/filterSmoothing trades variance for lag.
Velocity limit exceededReport; do not command hardware unless askedMeasurement and actuation/safety policy are separate concerns.

5. Likely prompt shape + reference architecture

Use for practice, not as a prediction of exact wording.

Practice prompt

“You have a servo object whose read_position() method returns its current angular position. Write a Python program that measures and reports the servo’s velocity as it moves. Assume you can call it repeatedly.”

Your first 90 seconds

  1. Restate: “I’ll estimate signed angular velocity from position samples and elapsed time.”
  2. Ask: Does the API provide timestamps? What are position units and wrap behavior?
  3. Ask: Is this one servo or multiple joints? Do you want each interval, a windowed estimate, or an average over a run?
  4. Ask: Standard library only? What should happen with invalid readings or non-increasing timestamps?
  5. Propose: “I’ll start with a small testable estimator using two samples, verify it, then add the live polling loop and robustness.”

Reference skeleton—know the reasoning, not the exact text

from dataclasses import dataclass
import math
from typing import Optional

@dataclass(frozen=True)
class Sample:
    timestamp_s: float
    position_rad: float

class VelocityEstimator:
    def __init__(self) -> None:
        self._previous: Optional[Sample] = None

    def update(self, sample: Sample) -> Optional[float]:
        if not (math.isfinite(sample.timestamp_s)
                and math.isfinite(sample.position_rad)):
            raise ValueError("sample values must be finite")

        previous = self._previous
        if previous is None:
            self._previous = sample
            return None

        dt = sample.timestamp_s - previous.timestamp_s
        if dt <= 0:
            # Deliberate policy: invalid input does not corrupt valid state.
            raise ValueError("timestamps must be strictly increasing")

        velocity = (sample.position_rad - previous.position_rad) / dt
        self._previous = sample
        return velocity

Then connect I/O without contaminating the estimator

import time
from collections.abc import Callable, Iterator

def sample_servo(
    read_position_rad: Callable[[], float],
    period_s: float,
) -> Iterator[Sample]:
    while True:
        # If the hardware gives an atomic position+timestamp sample, prefer it.
        position = read_position_rad()
        timestamp = time.monotonic()
        yield Sample(timestamp, position)
        time.sleep(period_s)

def run(read_position_rad: Callable[[], float], period_s: float = 0.02) -> None:
    estimator = VelocityEstimator()
    try:
        for sample in sample_servo(read_position_rad, period_s):
            velocity = estimator.update(sample)
            if velocity is not None:
                print(f"{velocity:.4f} rad/s")
    except KeyboardInterrupt:
        pass
Timing nuance worth saying: in this skeleton, the timestamp is taken after read_position_rad(). If reads have variable latency, the ideal API returns position and capture time together. If not, timestamping immediately before or after is an approximation; midpoint bracketing can estimate capture time if needed.

High-signal tests

def test_constant_positive_velocity():
    est = VelocityEstimator()
    assert est.update(Sample(10.0, 1.0)) is None
    assert est.update(Sample(10.25, 1.5)) == 2.0

def test_stationary():
    est = VelocityEstimator()
    est.update(Sample(0.0, 0.7))
    assert est.update(Sample(0.1, 0.7)) == 0.0

def test_rejects_duplicate_time_without_losing_previous_state():
    est = VelocityEstimator()
    est.update(Sample(1.0, 2.0))
    try:
        est.update(Sample(1.0, 99.0))
        assert False, "expected ValueError"
    except ValueError:
        pass
    assert est.update(Sample(2.0, 4.0)) == 2.0

Follow-ups to be ready for

  • Now the samples arrive in a CSV/log.
  • Now there are seven joints.
  • Measurements are noisy—make output stable.
  • Timestamps are irregular or sometimes duplicated.
  • The angle wraps from +π to −π.
  • Report average velocity over the last 200 ms.
  • Detect when speed exceeds a configured limit.
  • The read method blocks or occasionally raises.
  • How would you test this without a physical servo?
  • How would this design change if estimates feed a real-time controller rather than telemetry?

6. Servo-velocity Python question bank

One confirmed problem family, 40 targeted questions, a 33-question core servo track, Python solutions, and a repeatable drill system.

Confirmed familyExact wording unknown

The recruiter confirmed that you will write a Python script to measure servo velocity. No trustworthy public source reveals the exact prompt. Questions 1–12 below are deliberately constructed variants—not claimed leaks—ranked by how directly they exercise that confirmed task.

Daily practice selector

Attempt the selected problem from a blank file before opening its hints or solution. Repeat a problem until you can hit its target time while narrating clearly.

The daily and servo-random buttons draw only from the 33-question core. Use “all 40” when you want adjacent-company coverage too.

30:00

How to practice each question

  1. Minutes 0–3: read only the prompt; write the contract, assumptions, and 3 tests.
  2. Minutes 3–20: produce the simplest working implementation.
  3. Minutes 20–27: run tests, fix failures, and add the highest-value edge case.
  4. Minutes 27–30: state complexity, limitations, and the next improvement.
  5. Review: open Hint 1 only if stuck after 8 minutes; Hint 2 after 15; solution only after a real attempt.

Coverage audit: what the interviewer can vary

AxisVariations coveredQuestion map
Input formTwo values, batch arrays, iterator, live polling, async stream, CSV, serial packets, multiple jointsQ1–4, Q6, Q11, Q37–38
Position representationRadians, degrees, ticks, gear ratio, calibration, wrapped angle, continuous multi-turn angleQ5–6, Q21, Q26, Q33, Q39
TimeMonotonic clock, device time, ns conversion, jitter, duplicate/out-of-order samples, rollover, different streamsQ1–3, Q6, Q12, Q22, Q32
EstimatorBackward/central differences, interpolation, window regression, EMA, median, direct sensor velocityQ1–2, Q7–8, Q11, Q23, Q31, Q35–36
Bad dataNaN/∞, impossible jumps, noise, quantization, spikes, missing/stale samples, transient read failureQ1, Q7–10, Q24, Q27, Q34–35, Q40
RuntimeRate scheduling, missed deadlines, bounded buffers, backpressure, shutdown, retry, async timeoutQ3, Q12, Q24–25, Q37
OutputsSigned velocity, speed, per-joint vector, acceleration, overspeed events, health/confidenceQ4, Q9, Q12, Q28, Q40
TestingHand examples, injected clocks, simulation, state-after-error, black-box/HIL validationEvery coding card; especially Q10 and Q30
Control extensionCalibration/bring-up, PID, safety boundaries, observed-vs-commanded velocityQ19, Q26, Q29, Q31
Coverage verdict: no finite bank can guarantee every wording or hardware API, but this now covers the practical solution space around measuring servo velocity. A new prompt should be a recombination of these axes, not a fundamentally new problem.

Q1. Two-sample scalar velocity

FoundationHighest probability

Target: 8 minutes • Standard library • Skills: contract, units, validation

Prompt. Implement servo_velocity(p0, t0, p1, t1). Positions are radians and timestamps are seconds. Return signed angular velocity in rad/s. Decide what to do when time does not increase.
Hint 1

Velocity is displacement divided by elapsed time. Signed displacement preserves direction.

Hint 2

Validate finite numeric inputs and require t1 > t0. Do not return infinity for a duplicate timestamp.

Solution and reasoning
  1. Define the units and signed-output contract.
  2. Reject non-finite inputs so NaN cannot silently propagate.
  3. Compute dt and reject zero/negative intervals.
  4. Return Δposition / Δtime.
import math

def servo_velocity(p0: float, t0: float, p1: float, t1: float) -> float:
    values = (p0, t0, p1, t1)
    if not all(math.isfinite(x) for x in values):
        raise ValueError("positions and timestamps must be finite")
    dt = t1 - t0
    if dt <= 0:
        raise ValueError("timestamps must be strictly increasing")
    return (p1 - p0) / dt

assert servo_velocity(1.0, 10.0, 1.5, 10.25) == 2.0
assert servo_velocity(1.0, 0.0, 0.5, 0.25) == -2.0
assert servo_velocity(0.7, 3.0, 0.7, 4.0) == 0.0

Complexity: O(1) time and memory. Key interview statement: this is average velocity over the interval; with sufficiently close samples it approximates instantaneous velocity.

Q2. Streaming samples with constant memory

FoundationVery likely follow-up

Target: 15 minutes • Skills: generators, state, invalid-input policy

Prompt. Given any iterable of (timestamp_s, position_rad) pairs, yield one velocity for each interval without loading the stream into memory. The first sample produces no output.
Hint 1

Keep only the previous valid sample. A generator makes the function work with lists, files, and live streams.

Hint 2

Decide whether a bad sample should replace the previous valid state. A safe default is to raise before updating it.

Solution and reasoning
import math
from collections.abc import Iterable, Iterator

def velocities(
    samples: Iterable[tuple[float, float]],
) -> Iterator[tuple[float, float]]:
    previous: tuple[float, float] | None = None
    for timestamp, position in samples:
        if previous is None:
            previous = (timestamp, position)
            continue
        prev_t, prev_p = previous
        dt = timestamp - prev_t
        if dt <= 0:
            raise ValueError(f"non-increasing timestamp: {timestamp}")
        yield timestamp, (position - prev_p) / dt
        previous = (timestamp, position)

data = [(0.0, 1.0), (0.1, 1.2), (0.4, 1.8)]
result = list(velocities(data))
assert [t for t, _ in result] == [0.1, 0.4]
assert all(math.isclose(v, 2.0) for _, v in result)

Complexity: O(n) total time, O(1) auxiliary memory. Subtlety: the assignment to previous occurs after validation and calculation, so a rejected sample cannot corrupt valid state.

Q3. Poll a live servo at approximately 50 Hz

MediumPlausible live version

Target: 25 minutes • Skills: monotonic clock, scheduling, I/O separation, shutdown

Prompt. A servo exposes read_position_rad(). Print its measured velocity around 50 times per second until interrupted. Calls occasionally take a few milliseconds.
Hint 1

Use a monotonic clock for intervals. Separate one loop iteration from the infinite loop so it remains testable.

Hint 2

Repeatedly sleeping 20 ms after work makes the period “work time + 20 ms.” Schedule against an advancing deadline instead.

Solution and reasoning
import time
from collections.abc import Callable

def monitor_servo(
    read_position_rad: Callable[[], float],
    rate_hz: float = 50.0,
) -> None:
    if rate_hz <= 0:
        raise ValueError("rate_hz must be positive")
    period = 1.0 / rate_hz
    previous: tuple[float, float] | None = None
    deadline = time.monotonic()

    try:
        while True:
            # Best case: hardware returns position and capture time atomically.
            position = read_position_rad()
            now = time.monotonic()

            if previous is not None:
                prev_t, prev_p = previous
                dt = now - prev_t
                if dt > 0:
                    print(f"{(position - prev_p) / dt:.4f} rad/s")
            previous = (now, position)

            deadline += period
            delay = deadline - time.monotonic()
            if delay > 0:
                time.sleep(delay)
            else:
                # We missed one or more periods; don't burst to catch up.
                deadline = time.monotonic()
    except KeyboardInterrupt:
        return

Tradeoff to discuss: ordinary Python cannot promise hard real-time behavior. This is adequate for a practical measurement/telemetry loop, but a control/safety loop may need hardware timestamps, a real-time process, bounds, and explicit missed-deadline metrics.

Q4. Extend the estimator to a robot arm

Medium

Target: 20 minutes • Skills: dimensions, per-joint output, iterable safety

Prompt. Each sample contains a timestamp and positions for seven joints. Return the seven signed velocities. Reject a sample if its joint count changes.
Hint 1

Convert the positions to an immutable tuple at the boundary so a caller cannot mutate stored state.

Solution and reasoning
class JointVelocityEstimator:
    def __init__(self, joint_count: int) -> None:
        if joint_count <= 0:
            raise ValueError("joint_count must be positive")
        self.joint_count = joint_count
        self.previous: tuple[float, tuple[float, ...]] | None = None

    def update(self, timestamp: float, positions: list[float]) -> tuple[float, ...] | None:
        current = tuple(positions)
        if len(current) != self.joint_count:
            raise ValueError(
                f"expected {self.joint_count} joints, got {len(current)}"
            )
        if self.previous is None:
            self.previous = (timestamp, current)
            return None
        prev_t, prev_q = self.previous
        dt = timestamp - prev_t
        if dt <= 0:
            raise ValueError("timestamps must increase")
        result = tuple((q - old_q) / dt for q, old_q in zip(current, prev_q))
        self.previous = (timestamp, current)
        return result

est = JointVelocityEstimator(3)
assert est.update(0.0, [1.0, 2.0, 3.0]) is None
assert est.update(0.5, [2.0, 2.0, 2.0]) == (2.0, 0.0, -2.0)

Clarify whether the interviewer wants per-joint velocities, speed magnitudes, or end-effector velocity. Joint velocities do not directly equal Cartesian end-effector velocity; that conversion involves the manipulator Jacobian and is a different problem.

Q5. Correct an angle that wraps at ±π

Medium

Target: 15 minutes • Skills: circular quantities, boundary tests

Prompt. The encoder reports angles in [−π, π). A move from 179° to −179° is a small positive motion, not −358°. Modify the velocity calculation.
Hint 1

Normalize the raw difference into [−period/2, period/2).

Solution and reasoning
import math

def wrapped_delta(current: float, previous: float, period: float) -> float:
    if period <= 0:
        raise ValueError("period must be positive")
    return (current - previous + period / 2) % period - period / 2

def wrapped_velocity(p0: float, t0: float, p1: float, t1: float) -> float:
    dt = t1 - t0
    if dt <= 0:
        raise ValueError("timestamps must increase")
    return wrapped_delta(p1, p0, 2 * math.pi) / dt

eps = 1e-12
v = wrapped_velocity(math.radians(179), 0, math.radians(-179), 1)
assert abs(v - math.radians(2)) < eps
v = wrapped_velocity(math.radians(-179), 0, math.radians(179), 1)
assert abs(v - math.radians(-2)) < eps

Ambiguous boundary: exactly half a period has two equivalent directions; this formula chooses the negative direction. Document it if that case matters. Also ask whether the hardware already provides unwrapped angles.

Q6. Measure velocity from a large CSV log

Medium

Target: 30 minutes • Skills: file streaming, unit conversion, error reporting

Prompt. Read timestamp_ns,position_deg from a large CSV and write timestamp_ns,velocity_rad_s. Include useful errors for malformed rows.
Hint 1

Stream rows with csv.DictReader. Convert nanoseconds to seconds and degrees to radians before differentiation.

Solution and reasoning
import csv
import math
from typing import TextIO

def convert_log(source: TextIO, destination: TextIO) -> None:
    reader = csv.DictReader(source)
    required = {"timestamp_ns", "position_deg"}
    if not required.issubset(reader.fieldnames or []):
        raise ValueError(f"required columns: {sorted(required)}")

    writer = csv.DictWriter(
        destination, fieldnames=["timestamp_ns", "velocity_rad_s"]
    )
    writer.writeheader()
    previous: tuple[int, float] | None = None

    for line_number, row in enumerate(reader, start=2):
        try:
            timestamp_ns = int(row["timestamp_ns"])
            position_rad = math.radians(float(row["position_deg"]))
        except (TypeError, ValueError) as exc:
            raise ValueError(f"invalid row at line {line_number}") from exc

        if previous is not None:
            prev_ns, prev_rad = previous
            dt_s = (timestamp_ns - prev_ns) / 1_000_000_000
            if dt_s <= 0:
                raise ValueError(f"time did not increase at line {line_number}")
            writer.writerow({
                "timestamp_ns": timestamp_ns,
                "velocity_rad_s": (position_rad - prev_rad) / dt_s,
            })
        previous = (timestamp_ns, position_rad)

Policy question: fail fast versus skip-and-count malformed rows depends on the use case. For an interview, state the choice; do not silently discard data. Streaming gives O(1) auxiliary memory.

Q7. Estimate velocity from noisy, irregular samples

HardStrong follow-up

Target: 35 minutes • Skills: rolling window, linear regression, numerical stability

Prompt. Two-point differences are too noisy. Fit a line over the most recent N (time, position) samples and return its slope as velocity. Timestamps are irregular.
Hint 1

For a line q = a + vt, compute v = Σ(t−t̄)(q−q̄) / Σ(t−t̄)².

Hint 2

Center timestamps before multiplication. Use a bounded deque. Need at least two distinct timestamps.

Solution and reasoning
from collections import deque

class WindowSlope:
    def __init__(self, window_size: int) -> None:
        if window_size < 2:
            raise ValueError("window_size must be at least 2")
        self.samples: deque[tuple[float, float]] = deque(maxlen=window_size)

    def update(self, timestamp: float, position: float) -> float | None:
        if self.samples and timestamp <= self.samples[-1][0]:
            raise ValueError("timestamps must increase")
        self.samples.append((timestamp, position))
        if len(self.samples) < 2:
            return None

        mean_t = sum(t for t, _ in self.samples) / len(self.samples)
        mean_q = sum(q for _, q in self.samples) / len(self.samples)
        numerator = sum(
            (t - mean_t) * (q - mean_q) for t, q in self.samples
        )
        denominator = sum((t - mean_t) ** 2 for t, _ in self.samples)
        if denominator == 0:
            return None
        return numerator / denominator

est = WindowSlope(4)
assert est.update(10.0, 21.0) is None
assert abs(est.update(10.1, 21.2) - 2.0) < 1e-12
assert abs(est.update(10.4, 21.8) - 2.0) < 1e-12

Tradeoff: a larger window reduces variance but increases lag when velocity changes. This simple implementation is O(N) per sample; for a bounded small window that may be the right clarity/performance tradeoff. Running sums can make it O(1), but are easier to get wrong.

Q8. Smooth velocity without assuming fixed sample rate

Hard

Target: 30 minutes • Skills: EMA, time constant, jitter-aware filtering

Prompt. Apply exponential smoothing to raw velocity estimates, but samples arrive at irregular intervals. Configure the filter with a time constant τ rather than a fixed alpha.
Hint 1

Use alpha = 1 - exp(-dt / tau). This adapts the weight to actual elapsed time.

Solution and reasoning
import math

class SmoothedVelocity:
    def __init__(self, time_constant_s: float) -> None:
        if time_constant_s <= 0:
            raise ValueError("time constant must be positive")
        self.tau = time_constant_s
        self.previous_sample: tuple[float, float] | None = None
        self.filtered: float | None = None

    def update(self, timestamp: float, position: float) -> float | None:
        if self.previous_sample is None:
            self.previous_sample = (timestamp, position)
            return None
        prev_t, prev_q = self.previous_sample
        dt = timestamp - prev_t
        if dt <= 0:
            raise ValueError("timestamps must increase")
        raw = (position - prev_q) / dt
        alpha = 1.0 - math.exp(-dt / self.tau)
        self.filtered = (
            raw if self.filtered is None
            else self.filtered + alpha * (raw - self.filtered)
        )
        self.previous_sample = (timestamp, position)
        return self.filtered

Interpretation: after one time constant, the filter moves about 63% toward a step input. Smaller τ responds faster but passes more noise. Filtering velocity after differentiation is simple; filtering position before differentiating has different phase/noise behavior.

Q9. Detect sustained overspeed without false alarms

Medium

Target: 25 minutes • Skills: state machine, hysteresis, safety boundaries

Prompt. Raise an event when absolute velocity exceeds 2 rad/s for at least 100 ms. Clear it only after speed stays below 1.8 rad/s. Samples are timestamped and irregular.
Hint 1

Track when the threshold was first crossed. Use different entry and exit thresholds to avoid chatter.

Solution and reasoning
class OverspeedDetector:
    def __init__(self, enter: float, exit: float, dwell_s: float) -> None:
        if not (0 < exit < enter and dwell_s >= 0):
            raise ValueError("require 0 < exit < enter and nonnegative dwell")
        self.enter = enter
        self.exit = exit
        self.dwell_s = dwell_s
        self.above_since: float | None = None
        self.active = False

    def update(self, timestamp: float, velocity: float) -> bool:
        speed = abs(velocity)
        if self.active:
            if speed < self.exit:
                self.active = False
                self.above_since = None
            return self.active

        if speed > self.enter:
            if self.above_since is None:
                self.above_since = timestamp
            if timestamp - self.above_since >= self.dwell_s:
                self.active = True
        else:
            self.above_since = None
        return self.active

Important: this reports a condition; it does not command or stop hardware. Measurement, alerting, and safety actuation should remain explicit boundaries unless the prompt asks you to integrate them.

Q10. Test a measurement loop without a servo or real sleep

MediumHigh signal

Target: 25 minutes • Skills: dependency injection, fake clock, deterministic tests

Prompt. Refactor the measurement code so a test can simulate a servo moving at exactly 3 rad/s without waiting for wall-clock time.
Hint 1

Inject time and position sources. Do not call time.sleep inside the pure estimator test.

Solution and reasoning
from collections.abc import Callable

def take_measurement(
    read_time: Callable[[], float],
    read_position: Callable[[], float],
    previous: tuple[float, float] | None,
) -> tuple[tuple[float, float], float | None]:
    timestamp = read_time()
    position = read_position()
    current = (timestamp, position)
    if previous is None:
        return current, None
    prev_t, prev_q = previous
    dt = timestamp - prev_t
    if dt <= 0:
        raise ValueError("timestamps must increase")
    return current, (position - prev_q) / dt

class FakeMotion:
    def __init__(self, velocity: float) -> None:
        self.t = 0.0
        self.velocity = velocity
    def time(self) -> float:
        return self.t
    def position(self) -> float:
        return self.velocity * self.t

fake = FakeMotion(3.0)
state, velocity = take_measurement(fake.time, fake.position, None)
assert velocity is None
fake.t = 0.2
state, velocity = take_measurement(fake.time, fake.position, state)
assert abs(velocity - 3.0) < 1e-12

In production, prefer an atomic read_sample() returning both capture time and position. The separate callbacks here illustrate dependency injection, but also expose possible timestamp/position skew.

Q11. Compute offline velocities with central differences

Hard

Target: 35 minutes • Skills: numerical differentiation, endpoints, irregular spacing

Prompt. Given arrays of strictly increasing timestamps and positions, return one velocity per sample. Use one-sided differences at the endpoints and a three-point quadratic derivative for irregularly spaced interior samples.
Hint 1

For a simpler interview version, ask whether (q[i+1]-q[i-1])/(t[i+1]-t[i-1]) is acceptable. It is a secant across neighbors, but is not the exact three-point nonuniform quadratic formula.

Hint 2

Let h0=t[i]-t[i-1], h1=t[i+1]-t[i]. The derivative weights are -h1/(h0*(h0+h1)), (h1-h0)/(h0*h1), and h0/(h1*(h0+h1)).

Solution and reasoning
def offline_velocity(times: list[float], positions: list[float]) -> list[float]:
    if len(times) != len(positions) or len(times) < 2:
        raise ValueError("need equal arrays with at least two samples")
    if any(b <= a for a, b in zip(times, times[1:])):
        raise ValueError("times must be strictly increasing")

    n = len(times)
    result = [0.0] * n
    result[0] = (positions[1] - positions[0]) / (times[1] - times[0])
    result[-1] = (positions[-1] - positions[-2]) / (times[-1] - times[-2])

    for i in range(1, n - 1):
        h0 = times[i] - times[i - 1]
        h1 = times[i + 1] - times[i]
        w_prev = -h1 / (h0 * (h0 + h1))
        w_here = (h1 - h0) / (h0 * h1)
        w_next = h0 / (h1 * (h0 + h1))
        result[i] = (
            w_prev * positions[i - 1]
            + w_here * positions[i]
            + w_next * positions[i + 1]
        )
    return result

# q=t^2; exact derivative is 2t at the interior even with uneven samples.
t = [0.0, 1.0, 3.0]
q = [x * x for x in t]
v = offline_velocity(t, q)
assert abs(v[1] - 2.0) < 1e-12

Interview judgment: do not volunteer this formula unless the prompt needs it. For a live servo, it adds future-sample latency. The two-point estimator is usually the correct starting point.

Q12. Detect timing degradation in the measurement runtime

Hard

Target: 30 minutes • Skills: observability, jitter, dropped-sample inference

Prompt. The target rate is 50 Hz. Given sample timestamps, report actual mean rate, maximum interval, RMS jitter relative to 20 ms, and an estimated missed-period count.
Hint 1

Compute consecutive intervals. Jitter is dt - target_period. Estimate missed periods from how many target periods fit in a gap.

Solution and reasoning
import math
from dataclasses import dataclass

@dataclass(frozen=True)
class TimingHealth:
    mean_rate_hz: float
    max_interval_s: float
    rms_jitter_s: float
    estimated_missed: int

def timing_health(times: list[float], target_hz: float) -> TimingHealth:
    if len(times) < 2 or target_hz <= 0:
        raise ValueError("need two timestamps and a positive target rate")
    intervals = [b - a for a, b in zip(times, times[1:])]
    if any(dt <= 0 for dt in intervals):
        raise ValueError("timestamps must increase")
    target = 1.0 / target_hz
    mean_dt = sum(intervals) / len(intervals)
    rms_jitter = math.sqrt(
        sum((dt - target) ** 2 for dt in intervals) / len(intervals)
    )
    # A 2.1-period gap implies one likely missing sample.
    missed = sum(max(0, round(dt / target) - 1) for dt in intervals)
    return TimingHealth(1.0 / mean_dt, max(intervals), rms_jitter, missed)

h = timing_health([0.00, 0.02, 0.04, 0.08, 0.10], 50.0)
assert h.estimated_missed == 1
assert abs(h.mean_rate_hz - 40.0) < 1e-12

Nuance: “missed” is an estimate because timestamps alone do not prove whether the producer dropped a sample or intentionally paused. Production observability should distinguish source sequence numbers, acquisition failures, and consumer backpressure when possible.

Adjacent-company reported questions

The next eight cards come from candidate reports at companies with adjacent robotics work. They are not evidence that Physical Intelligence will ask the same questions. Their value is transfer: debugging, validation, robot-cell logic, calibration, real-time networking, and clear complexity analysis.

Q13. Control a robot cell with multiple conveyors

MediumReported: CovariantHigh transfer

Target: 35 minutes • Skills: state machine, events, fairness, fault handling

Reported theme. A Covariant robotics-SWE candidate reported being asked how to write code for a robot cell with multiple conveyors. Practice version: two conveyors produce pick requests, one robot serves them, and only one pick may be active. Design the controller.
Solution blueprint
  1. Clarify sensors/events, robot command API, priorities, cancellation, and fault behavior.
  2. Model explicit states: IDLE, PICKING, PLACING, FAULTED.
  3. Put incoming work into per-conveyor queues; choose round-robin or oldest-first to prevent starvation.
  4. Issue one idempotent command and transition only on matching completion events.
  5. On timeout/fault: stop conveyors if required, record context, and require an explicit recovery policy.
from collections import deque
from dataclasses import dataclass

@dataclass(frozen=True)
class Pick:
    conveyor: int
    item_id: str

class CellScheduler:
    def __init__(self, conveyor_count: int) -> None:
        self.queues = [deque() for _ in range(conveyor_count)]
        self.next_conveyor = 0
        self.active: Pick | None = None

    def submit(self, pick: Pick) -> None:
        self.queues[pick.conveyor].append(pick)

    def dispatch(self) -> Pick | None:
        if self.active is not None:
            return None
        for offset in range(len(self.queues)):
            index = (self.next_conveyor + offset) % len(self.queues)
            if self.queues[index]:
                self.active = self.queues[index].popleft()
                self.next_conveyor = (index + 1) % len(self.queues)
                return self.active
        return None

    def complete(self, item_id: str) -> None:
        if self.active is None or self.active.item_id != item_id:
            raise ValueError("completion does not match active pick")
        self.active = None

s = CellScheduler(2)
s.submit(Pick(0, "a")); s.submit(Pick(1, "b"))
assert s.dispatch() == Pick(0, "a")
s.complete("a")
assert s.dispatch() == Pick(1, "b")

Q14. Build a strict telemetry parser

FoundationReported: CovariantHigh transfer

Target: 20 minutes • Skills: schema validation, useful errors, immutable boundary

Reported theme. A Covariant candidate reported “data parsing and data validation.” Practice version: parse a JSON-like servo sample containing timestamp, joint positions, and units.
Solution and reasoning
import math
from dataclasses import dataclass
from typing import Any

@dataclass(frozen=True)
class JointSample:
    timestamp_s: float
    positions_rad: tuple[float, ...]

def parse_sample(data: dict[str, Any], joint_count: int) -> JointSample:
    required = {"timestamp_s", "positions", "unit"}
    missing = required - data.keys()
    if missing:
        raise ValueError(f"missing fields: {sorted(missing)}")
    if data["unit"] != "rad":
        raise ValueError("unit must be 'rad'")
    try:
        timestamp = float(data["timestamp_s"])
        positions = tuple(float(x) for x in data["positions"])
    except (TypeError, ValueError) as exc:
        raise ValueError("timestamp and positions must be numeric") from exc
    if len(positions) != joint_count:
        raise ValueError(f"expected {joint_count} joints, got {len(positions)}")
    if not all(math.isfinite(x) for x in (timestamp, *positions)):
        raise ValueError("all numeric values must be finite")
    return JointSample(timestamp, positions)

x = parse_sample({"timestamp_s": 1, "positions": [0, 0.5], "unit": "rad"}, 2)
assert x.positions_rad == (0.0, 0.5)

Validate at system boundaries, return a typed internal representation, and preserve context in errors. Do not accept ambiguous units and “fix them later.”

Q15. Debug and optimize an in-place matrix operation

MediumReported: Skild AILower PI relevance

Target: 25 minutes • Skills: clarification, mutation, complexity

Candidate report. A Skild AI robotics-SWE candidate described a live CoderPad task involving debugging and optimizing an in-place matrix “inversion,” plus time/space complexity. Because “inversion” is ambiguous in the report, the first correct move is to clarify whether it means transpose, 90° rotation, reversing rows/columns, or mathematical inverse.
Practice solution: in-place transpose of a square matrix
def transpose_in_place(matrix: list[list[int]]) -> None:
    n = len(matrix)
    if any(len(row) != n for row in matrix):
        raise ValueError("in-place transpose requires a square matrix")
    for row in range(n):
        for col in range(row + 1, n):
            matrix[row][col], matrix[col][row] = matrix[col][row], matrix[row][col]

m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transpose_in_place(m)
assert m == [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

O(n²) time and O(1) auxiliary space. Explain why only the upper triangle is visited; swapping every pair twice would undo the work.

Q16. Implement multi-head self-attention in PyTorch

HardReported: Skild AILow for this round

Target: awareness only for July 27 • Skills: tensor shapes, scaling, masks

Candidate report. A Skild AI post-training/deployment candidate reported being asked to write multi-head self-attention in PyTorch without autocomplete. This is real adjacent-company evidence, but it does not match your recruiter’s servo-script description.
Clear solution outline
  1. Project input (B,T,D) into Q, K, V.
  2. Reshape to (B,H,T,D/H) and transpose the head axis forward.
  3. Compute QKᵀ / sqrt(head_dim); apply mask before softmax.
  4. Multiply attention weights by V.
  5. Transpose/reshape heads back to (B,T,D); apply output projection.

Key checks: D % H == 0; mask broadcasting; softmax across key positions; avoid accidentally mixing batch/head dimensions. Study conceptually only unless later rounds explicitly mention ML coding.

Q17. Design a reliable pick-and-place workcell

HardReported: Intrinsic

Target: 25-minute verbal design • Skills: sensors, frames, calibration, validation

Candidate report. An Intrinsic senior robotics-SWE candidate reported a question about designing pick-and-place systems, including sensor suite and workcell design.
Solution framework
  1. Requirements: objects, poses, throughput, accuracy, payload, variability, humans, failure budget.
  2. Hardware: arm/reach/payload, gripper, conveyor/fixture, lighting, safety-rated devices.
  3. Perception: fixed and/or wrist RGB-D, object pose/confidence, occlusion strategy.
  4. Frames/calibration: camera intrinsics, camera-to-base extrinsics, tool center point, conveyor frame, time synchronization.
  5. Runtime: detect → select grasp → plan → execute → verify; explicit state machine and timeouts.
  6. Recovery: retry with new view/grasp, reject bin, human escalation, safe stop.
  7. Validation: unit/integration/simulation/HIL; success rate stratified by object/pose; cycle time; collision and near-miss metrics.

Tie back to velocity: joint-velocity quality is useful for execution monitoring, stall detection, and verifying that commanded motion matches observed motion.

Q18. Choose TCP or UDP for robot communication

MediumReported: Intrinsic

Target: 10-minute explanation • Skills: delivery semantics, latency, freshness

Candidate report. An Intrinsic SWE candidate reported being asked the difference between TCP and UDP. Practice variant: choose transports for servo telemetry and configuration commands.
Solution

TCP: ordered, reliable byte stream with retransmission and congestion control; good for configuration, firmware, logs, and commands that must arrive exactly and in order. A lost packet can delay later bytes (head-of-line blocking).

UDP: message/datagram based, no built-in delivery/order guarantee; good when freshness is more valuable than retransmitting stale telemetry. Add sequence number, source timestamp, schema/version, integrity check, and loss/reorder metrics at the application layer.

Safety caveat: neither transport alone makes commands safe. Define authorization, idempotency, acknowledgment, timeouts, stale-command rejection, watchdogs, and a safe state.

Q19. Handle an uncalibrated replacement motor

MediumReported: Figure AIHigh transfer

Target: 15-minute diagnosis • Skills: calibration, sign/offset/scale, safe bring-up

Candidate report. A Figure robotics-engineer candidate reported questions about actuators/motors and what to expect when swapping in an uncalibrated motor.
Solution framework
  1. Do not enable full-power motion immediately. Confirm part, gearing, encoder type/resolution, wiring, firmware, current/thermal limits, brakes, and mechanical installation.
  2. Establish encoder zero/homing reference, direction sign, counts-per-revolution/gear ratio, joint offset, and hard/soft limits.
  3. Verify position at known poses, then command very small low-current moves and confirm direction.
  4. Measure velocity independently where possible; compare commanded vs observed sign and magnitude.
  5. Tune or restore controller parameters only after kinematics/calibration are valid.
  6. Run staged acceptance tests: no-load, limited workspace, increasing speed/load, thermal soak; preserve rollback and calibration provenance.

High-signal failure modes: reversed sign causing positive feedback, wrong gear ratio scaling velocity, stale zero offset, phase/commutation mismatch, and a limit configuration copied from the wrong joint.

Q20. Find the shortest route through a grid

MediumReported: Amazon RoboticsLower PI relevance

Target: 25 minutes • Skills: BFS, queue, visited set

Candidate report. Amazon Robotics reports include BFS graph traversal. Practice version: return the minimum number of four-neighbor moves from start to goal through a binary occupancy grid.
Solution and reasoning
from collections import deque

def shortest_path(grid: list[list[int]], start: tuple[int, int], goal: tuple[int, int]) -> int | None:
    rows, cols = len(grid), len(grid[0])
    queue = deque([(start, 0)])
    visited = {start}
    while queue:
        (row, col), distance = queue.popleft()
        if (row, col) == goal:
            return distance
        for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            nxt = (row + dr, col + dc)
            r, c = nxt
            if 0 <= r < rows and 0 <= c < cols and grid[r][c] == 0 and nxt not in visited:
                visited.add(nxt)
                queue.append((nxt, distance + 1))
    return None

g = [[0, 1, 0], [0, 0, 0], [1, 0, 0]]
assert shortest_path(g, (0, 0), (2, 2)) == 4

BFS is optimal for equal-cost edges. Mark visited when enqueuing, not when dequeuing, to avoid duplicates. Real motion planning needs geometry, dynamics, collision margins, and often weighted/continuous methods.

High-transfer practical robotics extensions

Q21. Measure wheel velocity from encoder ticks

MediumHigh transfer

Target: 20 minutes • Skills: unit conversion, wraparound, gearing

Prompt. An encoder has 4096 counts/revolution and wraps at 4096. Given two tick counts and timestamps, return output-shaft rad/s for a 5:1 motor-to-output gear ratio.
Solution
import math

def tick_velocity(tick0: int, time0: float, tick1: int, time1: float,
                  counts_per_rev: int = 4096, gear_ratio: float = 5.0) -> float:
    dt = time1 - time0
    if dt <= 0 or counts_per_rev <= 0 or gear_ratio <= 0:
        raise ValueError("invalid time or encoder configuration")
    raw = tick1 - tick0
    half = counts_per_rev // 2
    delta_ticks = (raw + half) % counts_per_rev - half
    motor_radians = delta_ticks * (2 * math.pi / counts_per_rev)
    return motor_radians / gear_ratio / dt

# 100 motor ticks in 0.1 s, reduced by 5:1 at the output.
expected = 100 * 2 * math.pi / 4096 / 5 / 0.1
assert math.isclose(tick_velocity(4000, 0.0, 4, 0.1), expected)

Ask whether the counter can make more than half a revolution between samples; if so, two wrapped samples are insufficient to infer direction/count without a rate bound or unwrapped counter.

Q22. Pair positions with nearest timestamps

Medium

Target: 30 minutes • Skills: two pointers, tolerance, timestamp semantics

Prompt. Pair sorted position samples with the nearest sorted current-sensor sample if their timestamps differ by at most 5 ms. Each current sample may be used once.
Solution and reasoning
def pair_nearest(
    positions: list[tuple[float, float]],
    currents: list[tuple[float, float]],
    tolerance_s: float,
) -> list[tuple[tuple[float, float], tuple[float, float]]]:
    result = []
    j = 0
    for position in positions:
        t = position[0]
        while j + 1 < len(currents) and abs(currents[j + 1][0] - t) <= abs(currents[j][0] - t):
            j += 1
        if j < len(currents) and abs(currents[j][0] - t) <= tolerance_s:
            result.append((position, currents[j]))
            j += 1
        if j >= len(currents):
            break
    return result

p = [(0.010, 1.0), (0.030, 2.0)]
c = [(0.012, 4.0), (0.027, 5.0)]
assert len(pair_nearest(p, c, 0.005)) == 2

O(P+C) for sorted streams. Clarify whether reuse is allowed and whether timestamps share a clock. “Nearest” pairing can create causal problems online because the future sample may not have arrived yet.

Q23. Linearly interpolate a timestamped position

Foundation

Target: 15 minutes • Skills: interpolation, bounds, numerical contract

Prompt. Given bracketing samples (t0,q0) and (t1,q1), estimate position at t. Reject extrapolation.
Solution
def interpolate(t0: float, q0: float, t1: float, q1: float, t: float) -> float:
    if t1 <= t0:
        raise ValueError("bracket times must increase")
    if not t0 <= t <= t1:
        raise ValueError("requested time is outside the bracket")
    fraction = (t - t0) / (t1 - t0)
    return q0 + fraction * (q1 - q0)

assert interpolate(1.0, 10.0, 3.0, 20.0, 2.0) == 15.0

Linear interpolation assumes motion is approximately linear inside the interval. Angular wrap requires interpolation along the chosen shortest/continuous path, not raw values.

Q24. Make acquisition resilient to transient read failures

Medium

Target: 20 minutes • Skills: bounded retries, stale state, observability

Prompt. read_sample() occasionally raises TimeoutError. Retry transient failures, but never retry forever or pretend an old measurement is new.
Solution blueprint
  1. Catch only known transient exceptions, not every exception.
  2. Use a small bounded retry count/deadline; record attempts and final failure.
  3. Apply backoff only if it fits the measurement latency budget.
  4. Return/raise an explicit unavailable result; never retimestamp cached position.
  5. After repeated failures, trip a health state/watchdog and let a higher layer decide safe behavior.
import time
from collections.abc import Callable
from typing import TypeVar

T = TypeVar("T")

def read_with_retries(read: Callable[[], T], attempts: int = 3) -> T:
    if attempts < 1:
        raise ValueError("attempts must be positive")
    for attempt in range(attempts):
        try:
            return read()
        except TimeoutError:
            if attempt == attempts - 1:
                raise
            time.sleep(0.002 * (2 ** attempt))
    raise AssertionError("unreachable")

Q25. Prevent slow consumers from using stale telemetry

Medium

Target: 20 minutes • Skills: backpressure, bounded queues, freshness

Prompt. A producer samples at 100 Hz while the display sometimes updates at 20 Hz. Explain and implement a policy that bounds memory and favors the latest value.
Solution
from collections import deque
from threading import Lock
from typing import Generic, TypeVar

T = TypeVar("T")

class LatestValue(Generic[T]):
    def __init__(self) -> None:
        self._items: deque[T] = deque(maxlen=1)
        self._lock = Lock()

    def publish(self, value: T) -> None:
        with self._lock:
            self._items.append(value)

    def read(self) -> T | None:
        with self._lock:
            return self._items[-1] if self._items else None

b = LatestValue[int]()
b.publish(1); b.publish(2)
assert b.read() == 2

This is correct for a display where freshness beats completeness. It is wrong for an audit log or estimator that requires every sample. State the data-loss policy explicitly and count overwritten samples if observability matters.

Q26. Apply encoder offset, sign, and scale calibration

Foundation

Target: 15 minutes • Skills: calibration model, invariants, unit tests

Prompt. Convert a raw encoder count to joint radians using zero count, direction sign, counts/revolution, and gear ratio.
Solution
import math
from dataclasses import dataclass

@dataclass(frozen=True)
class EncoderCalibration:
    zero_count: int
    direction: int
    counts_per_rev: int
    gear_ratio: float

    def __post_init__(self) -> None:
        if self.direction not in (-1, 1):
            raise ValueError("direction must be -1 or 1")
        if self.counts_per_rev <= 0 or self.gear_ratio <= 0:
            raise ValueError("scale values must be positive")

    def radians(self, raw_count: int) -> float:
        motor_revs = self.direction * (raw_count - self.zero_count) / self.counts_per_rev
        return motor_revs * 2 * math.pi / self.gear_ratio

c = EncoderCalibration(100, -1, 4096, 4.0)
assert c.radians(100) == 0.0
assert math.isclose(c.radians(100 + 4096), -math.pi / 2)

Calibration metadata needs versioning and association with the exact joint/actuator. Mixing calibrated and raw values should be difficult at the API boundary.

Q27. Gate physically impossible samples

Medium

Target: 20 minutes • Skills: domain constraints, state integrity

Prompt. A servo cannot exceed 4 rad/s. Reject a new position sample if it implies faster motion, and ensure one rejected glitch does not poison future state.
Solution
class VelocityGate:
    def __init__(self, max_speed: float) -> None:
        if max_speed <= 0:
            raise ValueError("max_speed must be positive")
        self.max_speed = max_speed
        self.previous: tuple[float, float] | None = None

    def update(self, t: float, q: float) -> float | None:
        if self.previous is None:
            self.previous = (t, q)
            return None
        old_t, old_q = self.previous
        dt = t - old_t
        if dt <= 0:
            raise ValueError("timestamps must increase")
        velocity = (q - old_q) / dt
        if abs(velocity) > self.max_speed:
            raise ValueError("sample implies physically impossible speed")
        self.previous = (t, q)
        return velocity

g = VelocityGate(4.0)
g.update(0.0, 0.0)
try: g.update(0.1, 100.0)
except ValueError: pass
assert g.update(0.5, 1.0) == 2.0

A gate can also reject legitimate fast motion if configuration is wrong. Report rejected samples and avoid treating the threshold as a proof of sensor failure.

Q28. Extend velocity measurement to acceleration

Medium

Target: 20 minutes • Skills: second derivative, noise awareness

Prompt. Given timestamped velocity samples, estimate signed acceleration. What changes if velocities were themselves calculated from noisy positions?
Solution
def acceleration(v0: float, t0: float, v1: float, t1: float) -> float:
    dt = t1 - t0
    if dt <= 0:
        raise ValueError("timestamps must increase")
    return (v1 - v0) / dt

assert acceleration(1.0, 0.0, 3.0, 0.5) == 4.0

A second derivative amplifies noise even more strongly than velocity. For robust acceleration, consider fitting a quadratic to a window of positions and differentiating the fitted curve, or filter with a latency budget. Do not blindly difference already noisy two-point velocities.

Q29. Implement one safe, bounded PID update

HardAdjacent controls

Target: 30 minutes • Skills: state, dt, saturation, anti-windup

Prompt. Implement a discrete PID update from position error and elapsed time. Clamp output and prevent integral windup.
Solution blueprint
class PID:
    def __init__(self, kp: float, ki: float, kd: float, output_limit: float) -> None:
        if output_limit <= 0:
            raise ValueError("output_limit must be positive")
        self.kp, self.ki, self.kd = kp, ki, kd
        self.limit = output_limit
        self.integral = 0.0
        self.previous_error: float | None = None

    def update(self, error: float, dt: float) -> float:
        if dt <= 0:
            raise ValueError("dt must be positive")
        derivative = 0.0 if self.previous_error is None else (error - self.previous_error) / dt
        candidate_integral = self.integral + error * dt
        unclamped = self.kp * error + self.ki * candidate_integral + self.kd * derivative
        output = max(-self.limit, min(self.limit, unclamped))
        # Conditional integration: accept it if not saturated, or if error drives out of saturation.
        if output == unclamped or output * error < 0:
            self.integral = candidate_integral
        self.previous_error = error
        return output

p = PID(2.0, 0.0, 0.0, 5.0)
assert p.update(10.0, 0.1) == 5.0

Real control demands more: derivative filtering, bumpless reset, actuator dynamics, saturation/rate limits, stability analysis, safety supervision, and deterministic timing. Do not claim this snippet is production-ready control.

Q30. Test a black-box servo-velocity API

HardVery high transfer

Target: 20-minute test design • Skills: invariants, fixtures, measurement uncertainty

Prompt. You cannot inspect the implementation. Design tests that determine whether a servo’s reported velocity is correct, stable, timely, and uses the documented sign/units.
Solution framework
  1. Reference: compare against a calibrated external encoder/tachometer or high-rate position capture with quantified uncertainty.
  2. Static: stationary output near zero across orientations/temperatures; characterize bias/noise.
  3. Known profiles: constant positive/negative speeds, steps, ramps, and sinusoidal motion within safe limits.
  4. Semantics: sign, rad/s vs deg/s, output-shaft vs motor-shaft, gear ratio, timestamp point.
  5. Timing: latency, update rate, jitter, dropped/stale outputs, behavior during clock or communication faults.
  6. Boundaries: wrap crossing, near-zero speed, maximum rated speed, startup, stop, reconnect, calibration reload.
  7. Statistics: bias, MAE/RMSE, 95th/99th percentile error and latency; define pass thresholds before observing results.
  8. Reproducibility: recorded inputs, calibration version, hardware serial, firmware, temperature/load, and automated regression report.

Separate the sensor/API’s measurement error from the external reference’s uncertainty and time-alignment error. A beautiful error plot is meaningless if the signals are shifted in time.

Q31. Reconcile direct and position-derived velocity

MediumCore servo

Target: 20 minutes • Skills: API semantics, cross-checking, freshness

Prompt. The servo API returns both position and a firmware-computed velocity. Should your script use the supplied velocity or derive its own? Implement a comparison that flags sustained disagreement.
Solution
class VelocityCrossCheck:
    def __init__(self, tolerance: float, consecutive: int = 3) -> None:
        if tolerance < 0 or consecutive < 1:
            raise ValueError("invalid cross-check configuration")
        self.tolerance = tolerance
        self.consecutive = consecutive
        self.mismatch_count = 0

    def update(self, supplied: float, derived: float) -> bool:
        if abs(supplied - derived) > self.tolerance:
            self.mismatch_count += 1
        else:
            self.mismatch_count = 0
        return self.mismatch_count >= self.consecutive

c = VelocityCrossCheck(0.2, 2)
assert not c.update(1.5, 1.0)
assert c.update(1.5, 1.0)

Prefer the firmware value if its definition, units, timestamps, filtering, and latency satisfy the contract; it may use higher-rate encoder data. Deriving your own is valuable for validation and when only position is exposed. Comparing them requires time alignment—filter delay alone can create apparent disagreement during acceleration.

Q32. Handle a wrapping hardware timer

HardCore servo

Target: 20 minutes • Skills: unsigned rollover, units, ambiguity bound

Prompt. Device timestamps are unsigned 32-bit microseconds and wrap back to zero. Calculate elapsed seconds between samples.
Solution
def elapsed_u32_us(previous: int, current: int) -> float:
    modulus = 1 << 32
    if not (0 <= previous < modulus and 0 <= current < modulus):
        raise ValueError("timestamps must be uint32 values")
    delta_us = (current - previous) % modulus
    if delta_us == 0:
        raise ValueError("zero elapsed device time")
    return delta_us / 1_000_000

assert elapsed_u32_us((1 << 32) - 100, 100) == 0.0002

This assumes fewer than one full counter period elapsed between samples. Without that bound, rollover count is unknowable from two values. For a 32-bit microsecond timer the full period is about 71.6 minutes.

Q33. Convert wrapped angles into continuous position

MediumCore servo

Target: 20 minutes • Skills: phase unwrapping, continuity assumption

Prompt. The sensor reports angles in [−π, π). Return a continuous multi-turn angle sequence suitable for differentiating.
Solution
import math

def unwrap_angles(angles: list[float], period: float = 2 * math.pi) -> list[float]:
    if not angles:
        return []
    result = [angles[0]]
    previous_wrapped = angles[0]
    for current in angles[1:]:
        delta = (current - previous_wrapped + period / 2) % period - period / 2
        result.append(result[-1] + delta)
        previous_wrapped = current
    return result

a = [math.radians(170), math.radians(-170), math.radians(-150)]
u = unwrap_angles(a)
assert all(math.isclose(math.degrees(x), y) for x, y in zip(u, [170, 190, 210]))

Unwrapping assumes true motion between samples is less than half a period. If the servo can move faster, sample faster or use a multi-turn encoder/turn counter.

Q34. Estimate low velocity with encoder quantization

HardCore servo

Target: 20-minute reasoning • Skills: resolution, event timing, window choice

Prompt. At low speed, consecutive position readings are identical for many samples and then jump by one tick. Two-point velocity alternates between zero and spikes. Improve the estimate.
Solution framework
  1. Quantify one-tick angle: 2π / (counts_per_rev × gear_ratio).
  2. Use a longer time/window slope so several samples or ticks contribute; report latency.
  3. For very low speed, measure time between encoder edges (reciprocal method) instead of ticks per fixed interval.
  4. Apply a zero-speed timeout when no edge arrives; otherwise the last reciprocal estimate can remain stale forever.
  5. Return a quality indicator based on tick count/window duration.

Do not “fix” quantization by inventing sub-tick motion unless the estimator’s model and uncertainty are explicit. Larger windows reduce quantization variance at the price of slower response.

Q35. Remove isolated velocity spikes with a median filter

MediumCore servo

Target: 15 minutes • Skills: robust statistics, fixed window

Prompt. A stream is usually smooth but contains occasional one-sample velocity spikes. Return a rolling median over an odd-sized window.
Solution
from collections import deque
from statistics import median

class RollingMedian:
    def __init__(self, window: int) -> None:
        if window < 1 or window % 2 == 0:
            raise ValueError("window must be a positive odd number")
        self.values: deque[float] = deque(maxlen=window)

    def update(self, value: float) -> float:
        self.values.append(value)
        return float(median(self.values))

m = RollingMedian(3)
assert [m.update(x) for x in (1.0, 100.0, 1.2, 1.1)][-1] == 1.2

A median is robust to isolated impulses but nonlinear and lagging. It can suppress a legitimate abrupt velocity change; use it when spike behavior matches the sensor fault model.

Q36. Fit velocity over the last 200 ms

HardCore servo

Target: 30 minutes • Skills: time window, irregular rate, least-squares slope

Prompt. Samples arrive irregularly. Keep only samples from the last 200 ms and estimate velocity by linear regression.
Solution
from collections import deque

class TimedWindowSlope:
    def __init__(self, duration_s: float) -> None:
        if duration_s <= 0:
            raise ValueError("duration must be positive")
        self.duration = duration_s
        self.samples: deque[tuple[float, float]] = deque()

    def update(self, t: float, q: float) -> float | None:
        if self.samples and t <= self.samples[-1][0]:
            raise ValueError("timestamps must increase")
        self.samples.append((t, q))
        cutoff = t - self.duration
        while len(self.samples) > 1 and self.samples[1][0] <= cutoff:
            self.samples.popleft()
        if len(self.samples) < 2:
            return None
        mean_t = sum(x for x, _ in self.samples) / len(self.samples)
        mean_q = sum(x for _, x in self.samples) / len(self.samples)
        denominator = sum((x - mean_t) ** 2 for x, _ in self.samples)
        return sum((x - mean_t) * (y - mean_q) for x, y in self.samples) / denominator

w = TimedWindowSlope(0.2)
assert w.update(0.0, 0.0) is None
assert w.update(0.1, 0.2) == 2.0

A time window provides consistent temporal smoothing under rate jitter. State the boundary policy: this version retains one sample at/before the cutoff when needed to maintain a useful span.

Q37. Consume an asynchronous servo stream safely

HardCore servo

Target: 30 minutes • Skills: async iterator, timeout, cancellation

Prompt. Samples arrive through an async iterator. Calculate velocity and fail if no sample arrives for 100 ms.
Solution blueprint
import asyncio
from collections.abc import AsyncIterator

async def async_velocities(
    samples: AsyncIterator[tuple[float, float]], timeout_s: float = 0.1
) -> AsyncIterator[float]:
    iterator = aiter(samples)
    previous: tuple[float, float] | None = None
    while True:
        try:
            current = await asyncio.wait_for(anext(iterator), timeout_s)
        except StopAsyncIteration:
            return
        if previous is not None:
            dt = current[0] - previous[0]
            if dt <= 0:
                raise ValueError("timestamps must increase")
            yield (current[1] - previous[1]) / dt
        previous = current

Let cancellation propagate; do not catch CancelledError unless cleanup requires it. Decide whether a timeout should end the stream, emit a health event, or reconnect—do not silently block forever.

Q38. Validate servo samples from a serial protocol

HardCore servo

Target: 30 minutes • Skills: framing, checksum, sequence gaps, partial reads

Prompt. Serial messages contain sequence,timestamp_us,position_ticks,checksum. Explain how the acquisition layer validates them before velocity estimation.
Solution framework
  1. Buffer arbitrary byte chunks; extract only complete frames using length/delimiter plus escaping rules.
  2. Verify checksum/CRC before parsing fields or updating estimator state.
  3. Validate schema/version, ranges, finite/unit conversions, and device timestamp progression.
  4. Use sequence numbers to count loss, duplicates, and reordering; define wrap behavior.
  5. Keep corrupted packets out of valid state. Report counters with last error context.
  6. Convert ticks/time with calibration, then feed the pure estimator.

The interviewer may provide a simplified comma-separated line. Even then, handle partial reads at the adapter layer and keep packet validation separate from velocity math.

Q39. Convert among RPM, rad/s, and output-shaft speed

FoundationCore servo

Target: 10 minutes • Skills: dimensional analysis, gearing

Prompt. A motor spins at 3000 RPM through a 50:1 reduction. Return output-shaft angular velocity in rad/s.
Solution
import math

def motor_rpm_to_output_rad_s(motor_rpm: float, reduction: float) -> float:
    if reduction <= 0:
        raise ValueError("reduction must be positive")
    motor_rad_s = motor_rpm * 2 * math.pi / 60
    return motor_rad_s / reduction

assert math.isclose(motor_rpm_to_output_rad_s(3000, 50), 2 * math.pi)

Write units through the calculation: rev/min × 2π rad/rev × 1 min/60 s ÷ gear ratio. Ask which direction the ratio is defined and whether sign comes from motor orientation.

Q40. Report velocity plus measurement quality

MediumCore servo

Target: 20 minutes • Skills: honest API, stale/invalid states, metadata

Prompt. Design an output type that distinguishes startup, valid, stale, and invalid estimates and carries the interval used.
Solution
from dataclasses import dataclass
from enum import Enum, auto

class Quality(Enum):
    STARTUP = auto()
    VALID = auto()
    STALE = auto()
    INVALID = auto()

@dataclass(frozen=True)
class VelocityReading:
    velocity_rad_s: float | None
    timestamp_s: float
    interval_s: float | None
    quality: Quality
    reason: str = ""

startup = VelocityReading(None, 1.0, None, Quality.STARTUP, "need a second sample")
assert startup.velocity_rad_s is None

A numeric zero must mean measured stationary—not “no estimate.” Quality metadata prevents downstream code from confusing missing/stale data with real zero velocity. Define staleness using source timestamp and the consumer’s clock relationship.

Recommended repetition schedule

DayTimed drillReview drillMastery criterion
Cycle 1Q1 + Q2Read Q3 solutionBoth correct in 20 min total
Cycle 2Q3Q4 testsWorking baseline by min 18
Cycle 3Q5 + Q6Explain units aloudBoundary and conversion tests pass
Cycle 4Q7Q8 tradeoffsDerive slope without notes
Cycle 5Q9 + Q10Refactor Q3Deterministic tests, no real sleep
Cycle 6Q11 or Q12Redo weakest questionCan explain why complexity is justified
Cycle 7Q13 + Q14Q17 verbal designExplicit state/fault policy
Cycle 8Q19 + Q21Calibration checklistCorrect sign/scale/wrap
Cycle 9Q22 + Q24Q25 tradeoffClock and stale-data policy explicit
Cycle 10Q27 + Q30Redo weakest core questionState protected after bad data
Cycle 11Q31 + Q32 + Q33Clock/wrap assumptionsCrossing rollover stays correct
Cycle 12Q34 + Q35 + Q36Filter comparisonCan explain noise–latency tradeoff
Cycle 13Q37 + Q38Failure-state walkthroughNo stale/corrupt state accepted
Cycle 14Q39 + Q40Redo Q1 from blankUnits and quality semantics explicit
Final cycleRandom Q1–Q14 or Q19–Q30One 60-minute mock≥16/20 rubric and calm narration

7. Mock interview bank

Do these from blank files. Reveal hints only during review.

Prompt A — streaming single servo (foundation)

Implement measure_velocity(samples), where samples is an iterable of (timestamp_s, position_rad). Yield one signed velocity per valid interval. Define behavior for the first sample and invalid time.

Interviewer follow-ups

  1. Accept a generator too; do not materialize it.
  2. Add angular wraparound with configurable period.
  3. Explain the complexity.

Target: O(1) memory, O(n) time, tests for stationary/negative/irregular/invalid time.

Prompt B — live servo API (most plausible)

You receive a servo.read_position() method. Report velocity at about 50 Hz until interrupted. The method is slow sometimes.

Follow-ups at fixed times

  1. Minute 25: support seven joints.
  2. Minute 40: reduce noise.
  3. Minute 50: explain how you would ensure missed deadlines do not accumulate drift.

Look for: monotonic time, separation of I/O/estimation, schedule based on deadlines rather than repeated blind sleeps, dimensions, filter lag.

Prompt C — offline log + dirty data

Read a CSV of timestamp_ns,position_deg and output timestamp_ns,velocity_rad_s. Rows may be empty, malformed, duplicated, or out of order. Write code suitable for a large file.

Decisions

Stream vs sort? Skip vs fail? Header handling? Conversion degrees→radians and ns→seconds? Line-numbered errors? Make policies explicit.

Prompt D — robust windowed estimate

Position samples have Gaussian-ish noise and irregular timestamps. Implement velocity as a least-squares slope over the most recent N samples. Return None until at least two distinct timestamps exist.

Follow-ups

  1. Use a time window rather than N.
  2. Extend to multiple joints.
  3. Discuss numerical stability for epoch-scale timestamps.
Prompt E — test-design discussion

You cannot access real hardware in CI. Design a test strategy for a velocity measurement component.

  • Deterministic fake servo driven by a known function q(t).
  • Injected clock rather than real sleep.
  • Unit tests for pure estimator.
  • Property tests: constant velocity; translation in time/position does not change result; scaling position scales velocity.
  • Recorded-log regression tests.
  • Hardware-in-loop smoke test for units, sign, rate, and wrap.
  • Metrics: error distribution, latency, dropped samples, invalid-sample count.

20-point mock rubric

Category01234
ClarificationCodes blindlyOne superficial askUnits or API clarifiedMost contract clarifiedConcise, prioritized contract + assumptions
CorrectnessNo working coreMajor formula/state bugHappy path onlyCorrect core + key edge casesCorrect, coherent policies, units, state
Code qualityUnusableTangledReadable but coupledClear functions/names/separationSimple, extensible, idiomatic
Testing/debuggingNoneManual happy path2 basic casesStrong representative suiteSystematic tests + evidence-led debugging
CommunicationSilent/combativeHard to followSome narrationClear collaborative reasoningExcellent prioritization and tradeoffs

Pass target: 16/20 twice. Any score below 2 in correctness or communication requires focused repair even if total is high.

8. The 60-minute performance script

A time budget that keeps you from vanishing down a rabbit hole.

MinuteActionUseful language
0–3Listen, restate, clarify high-impact ambiguity.“I’m hearing that we want signed angular velocity from sequential position readings. Before I choose the interface: do readings include timestamps and what are the units/wrap semantics?”
3–6Give a thin plan and edge-case priority.“I’ll make estimation pure and testable, verify two-point differentiation, then connect the servo loop. After that I’ll handle non-increasing time and whichever robustness case matters most.”
6–20Implement working baseline.Narrate structure and invariants, not every keystroke.
20–28Run hand example/tests; fix real failures.“For 0.5 rad in 0.25 s, I expect 2 rad/s. I’m testing the sign and units explicitly.”
28–43Add requested I/O/extension.“The baseline is working. Of filtering, multi-axis support, and malformed timestamps, which would you like me to prioritize?”
43–53Edge cases, tests, cleanups.“I’m choosing not to update state after a bad timestamp so one invalid reading doesn’t corrupt the next interval.”
53–58Review behavior, complexity, limitations.“This is O(1) memory and one pass. The main limitation is noise amplification; for telemetry I’d fit a short-window slope, while a controller needs a latency-aware choice.”
58–60Invite questions.“Would you like me to go deeper on timing accuracy, filtering, or how I’d validate against the real servo?”

Communication rules

  • Think in headlines. “I’m separating acquisition from estimation so tests need no hardware.”
  • Make assumptions visible. Do not hide uncertainty in code.
  • Test before polishing. A passing tiny example creates shared confidence.
  • When stuck: restate the invariant, create the smallest failing example, inspect values/units, then change one thing.
  • When interrupted: stop typing, listen, restate the change, name what remains valid, reprioritize.
  • Accept hints productively. “That makes sense; I’ll use a monotonic clock because we care about intervals, not civil time.”
Avoid: silently coding for 15 minutes; claiming real-time guarantees from ordinary Python; adding concurrency without need; using a filter you cannot explain; sorting a live out-of-order stream without discussion; confusing degrees and radians; reporting speed when signed velocity was requested.

9. Physical Intelligence context

Enough to connect your work to theirs—without cramming the entire research program.

What the public technical material says

  • Physical Intelligence develops general-purpose vision-language-action robot policies and publishes the experimental openpi codebase.
  • The openpi documentation describes multiple robot embodiments and action spaces. Joint angles use radians; common control frequencies listed are 20 Hz for UR5e/Franka and 50 Hz for ARX/Trossen, while DROID uses seven joint-velocity action dimensions at 15 Hz.
  • The π0.5 company post describes predicting a 50-step, one-second continuous low-level action chunk after a high-level subtask—an illustration of why runtime timing, action semantics, and reliable robot interfaces matter.
  • PI’s GitHub response says internal π0 data used joint positions, while DROID uses joint velocity; action/state representation is embodiment-dependent rather than universally unified.

A credible “why PI / why this task” answer

“I’m excited by the boundary between learned policies and physical systems. The model can be sophisticated, but its value depends on mundane runtime details being correct: timestamps, units, action representation, sampling jitter, validation, and failure behavior. A servo-velocity task is a small version of that boundary, and I like engineering where correctness has a physical meaning you can measure.”

Smart end-of-interview questions

  1. “What are the hardest reliability problems at the boundary between learned action chunks and the robot runtime today?”
  2. “How does the team decide what belongs in a shared runtime abstraction versus robot-specific integration code?”
  3. “What does excellent performance in this role look like in the first three months?”
  4. “How do you validate timing and action semantics across embodiments with different control rates and interfaces?”
  5. “What surprised the team most when moving policies from offline evaluation onto real robots?”

10. Research trail and reading order

Primary sources first; public interview evidence clearly bounded.

  1. Your recruiter email (primary, private): exact format clue—one hour, practical Python, measure servo velocity, no LeetCode.
  2. Physical Intelligence openpi README — company’s public models/code and runtime/inference entry points.
  3. openpi action-space and normalization documentation — radians, dimensions, and platform control frequencies.
  4. Physical Intelligence GitHub discussion #302 — direct maintainer explanation of joint position, end-effector, and DROID joint-velocity representations.
  5. Physical Intelligence π0.5 post — high/low-level inference and one-second action chunks.
  6. Physical Intelligence openpi release post — goals and limitations of the open source release.
  7. Python time documentation — monotonic and performance clocks.
  8. NumPy gradient documentation — numerical derivatives and spacing.
  9. SciPy Savitzky–Golay documentation — optional smoothing/derivative extension.
  10. Glassdoor Physical Intelligence interview page — one unrelated test-pilot report only; weak evidence for this technical role.
  11. Covariant candidate reports — robot-cell/multiple-conveyor code, basic Python, parsing, and validation themes.
  12. Skild AI candidate reports — live debugging/optimization, an in-place matrix task, complexity discussion, and PyTorch attention for a different role.
  13. Intrinsic candidate reports — pick-and-place workcell design, model-based robotics/computer vision, basic coding, and TCP versus UDP.
  14. Figure AI candidate reports — actuator/motor understanding and replacing an uncalibrated motor.
  15. Amazon Robotics candidate reports — BFS/hash-style coding and behavioral questions; included only as lower-relevance adjacent evidence.

Minimal reading order (90 minutes total)

  1. openpi action-space doc (20 min)
  2. Python time docs: monotonic/perf counter (15 min)
  3. GitHub discussion #302 (10 min)
  4. π0.5 post, focus on runtime/action chunk sections (25 min)
  5. NumPy gradient + SciPy Savitzky–Golay overview (20 min)

Research checked July 19, 2026. Public pages change; the recruiter email remains the controlling source for interview format.