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.
Return a testable value rather than burying logic inside I/O.
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.
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.
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.
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.
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.
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
Method
Strength
Weakness
Best interview use
Two-point backward difference
Simple, online, minimal latency
Noisy
Always build this baseline first
Central difference
Usually more accurate at interior points
Needs a future sample; endpoint handling
Offline arrays/logs
Window endpoints
Reduces some noise
Lag; ignores interior structure
Quick windowed improvement
Least-squares slope
Uses every point; handles irregular times
Window lag and more code
Strong practical extension
EMA of raw velocity
Constant memory, tunable smoothing
Phase lag; parameter depends on timing
Streaming telemetry
Savitzky–Golay derivative
Smooth derivative, preserves shapes
Dependency, parameter constraints, typically offline/windowed
Discuss 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
Condition
Safe default
Say aloud
First sample
Return None; cache it
There is not yet an interval.
dt == 0
Reject/skip explicitly
Division is undefined; duplicate timestamp may indicate bad data.
dt < 0
Reject, do not silently sort a live stream
Out-of-order data or mixed clock domains.
NaN/∞
Validate with math.isfinite
Invalid samples should not poison estimator state.
Tiny dt
Optionally enforce minimum interval
Quantization/noise creates huge estimates.
Large gap
Return average over gap or reset, per contract
It may no longer represent instantaneous behavior.
Changing joint count
Raise a descriptive error
Dimensional consistency is part of the API.
Position noise
Baseline first; optional window/filter
Smoothing trades variance for lag.
Velocity limit exceeded
Report; do not command hardware unless asked
Measurement 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
Restate: “I’ll estimate signed angular velocity from position samples and elapsed time.”
Ask: Does the API provide timestamps? What are position units and wrap behavior?
Ask: Is this one servo or multiple joints? Do you want each interval, a windowed estimate, or an average over a run?
Ask: Standard library only? What should happen with invalid readings or non-increasing timestamps?
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.
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
Minutes 0–3: read only the prompt; write the contract, assumptions, and 3 tests.
Minutes 3–20: produce the simplest working implementation.
Minutes 20–27: run tests, fix failures, and add the highest-value edge case.
Minutes 27–30: state complexity, limitations, and the next improvement.
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
Axis
Variations covered
Question map
Input form
Two values, batch arrays, iterator, live polling, async stream, CSV, serial packets, multiple joints
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.
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
Define the units and signed-output contract.
Reject non-finite inputs so NaN cannot silently propagate.
Compute dt and reject zero/negative intervals.
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.
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.
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.
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.
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.
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
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
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
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.
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
Clarify sensors/events, robot command API, priorities, cancellation, and fault behavior.
Model explicit states: IDLE, PICKING, PLACING, FAULTED.
Put incoming work into per-conveyor queues; choose round-robin or oldest-first to prevent starvation.
Issue one idempotent command and transition only on matching completion events.
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")
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
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
Project input (B,T,D) into Q, K, V.
Reshape to (B,H,T,D/H) and transpose the head axis forward.
Compute QKᵀ / sqrt(head_dim); apply mask before softmax.
Multiply attention weights by V.
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.
Candidate report. An Intrinsic senior robotics-SWE candidate reported a question about designing pick-and-place systems, including sensor suite and workcell design.
Frames/calibration: camera intrinsics, camera-to-base extrinsics, tool center point, conveyor frame, time synchronization.
Runtime: detect → select grasp → plan → execute → verify; explicit state machine and timeouts.
Recovery: retry with new view/grasp, reject bin, human escalation, safe stop.
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.
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.
Candidate report. A Figure robotics-engineer candidate reported questions about actuators/motors and what to expect when swapping in an uncalibrated motor.
Solution framework
Do not enable full-power motion immediately. Confirm part, gearing, encoder type/resolution, wiring, firmware, current/thermal limits, brakes, and mechanical installation.
Establish encoder zero/homing reference, direction sign, counts-per-revolution/gear ratio, joint offset, and hard/soft limits.
Verify position at known poses, then command very small low-current moves and confirm direction.
Measure velocity independently where possible; compare commanded vs observed sign and magnitude.
Tune or restore controller parameters only after kinematics/calibration are valid.
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.
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
Catch only known transient exceptions, not every exception.
Use a small bounded retry count/deadline; record attempts and final failure.
Apply backoff only if it fits the measurement latency budget.
Return/raise an explicit unavailable result; never retimestamp cached position.
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
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?
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.
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
Reference: compare against a calibrated external encoder/tachometer or high-rate position capture with quantified uncertainty.
Static: stationary output near zero across orientations/temperatures; characterize bias/noise.
Known profiles: constant positive/negative speeds, steps, ramps, and sinusoidal motion within safe limits.
Semantics: sign, rad/s vs deg/s, output-shaft vs motor-shaft, gear ratio, timestamp point.
Timing: latency, update rate, jitter, dropped/stale outputs, behavior during clock or communication faults.
Statistics: bias, MAE/RMSE, 95th/99th percentile error and latency; define pass thresholds before observing results.
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.
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
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
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.
Use a longer time/window slope so several samples or ticks contribute; report latency.
For very low speed, measure time between encoder edges (reciprocal method) instead of ticks per fixed interval.
Apply a zero-speed timeout when no edge arrives; otherwise the last reciprocal estimate can remain stale forever.
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
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.
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.
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
Prompt. Serial messages contain sequence,timestamp_us,position_ticks,checksum. Explain how the acquisition layer validates them before velocity estimation.
Solution framework
Buffer arbitrary byte chunks; extract only complete frames using length/delimiter plus escaping rules.
Verify checksum/CRC before parsing fields or updating estimator state.
Validate schema/version, ranges, finite/unit conversions, and device timestamp progression.
Use sequence numbers to count loss, duplicates, and reordering; define wrap behavior.
Keep corrupted packets out of valid state. Report counters with last error context.
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
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.
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
Day
Timed drill
Review drill
Mastery criterion
Cycle 1
Q1 + Q2
Read Q3 solution
Both correct in 20 min total
Cycle 2
Q3
Q4 tests
Working baseline by min 18
Cycle 3
Q5 + Q6
Explain units aloud
Boundary and conversion tests pass
Cycle 4
Q7
Q8 tradeoffs
Derive slope without notes
Cycle 5
Q9 + Q10
Refactor Q3
Deterministic tests, no real sleep
Cycle 6
Q11 or Q12
Redo weakest question
Can explain why complexity is justified
Cycle 7
Q13 + Q14
Q17 verbal design
Explicit state/fault policy
Cycle 8
Q19 + Q21
Calibration checklist
Correct sign/scale/wrap
Cycle 9
Q22 + Q24
Q25 tradeoff
Clock and stale-data policy explicit
Cycle 10
Q27 + Q30
Redo weakest core question
State protected after bad data
Cycle 11
Q31 + Q32 + Q33
Clock/wrap assumptions
Crossing rollover stays correct
Cycle 12
Q34 + Q35 + Q36
Filter comparison
Can explain noise–latency tradeoff
Cycle 13
Q37 + Q38
Failure-state walkthrough
No stale/corrupt state accepted
Cycle 14
Q39 + Q40
Redo Q1 from blank
Units and quality semantics explicit
Final cycle
Random Q1–Q14 or Q19–Q30
One 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
Accept a generator too; do not materialize it.
Add angular wraparound with configurable period.
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
Minute 25: support seven joints.
Minute 40: reduce noise.
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
Use a time window rather than N.
Extend to multiple joints.
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.
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.
Minute
Action
Useful language
0–3
Listen, 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–6
Give 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–20
Implement working baseline.
Narrate structure and invariants, not every keystroke.
20–28
Run 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–43
Add requested I/O/extension.
“The baseline is working. Of filtering, multi-axis support, and malformed timestamps, which would you like me to prioritize?”
43–53
Edge 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–58
Review 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–60
Invite 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
“What are the hardest reliability problems at the boundary between learned action chunks and the robot runtime today?”
“How does the team decide what belongs in a shared runtime abstraction versus robot-specific integration code?”
“What does excellent performance in this role look like in the first three months?”
“How do you validate timing and action semantics across embodiments with different control rates and interfaces?”
“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.
Your recruiter email (primary, private): exact format clue—one hour, practical Python, measure servo velocity, no LeetCode.