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

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 question bank

One confirmed problem family, twelve increasingly realistic variants, complete 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.

Choose “Today’s drill” for a deterministic daily question or “Random drill” for a fresh draw.

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.

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.

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
Final cycleRandom questionOne 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.

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.