Interview guides · Carnegie Robotics
Carnegie Robotics Software Engineer Interview Guide
Carnegie Robotics Software Engineer interview guide: reported rounds, C++, geometry, profiling and debugging questions, and a seven-day prep plan.
The 60-second brief
Candidates describe Carnegie Robotics as a company whose software lets autonomous systems perceive, navigate and operate outside the lab, with applications reported from defense to agriculture. The Software Engineer role reported here covers middleware, message serialization, process management, loggers fast enough to saturate NVMe drives, over-the-air update mechanisms, CUDA kernels for GPU acceleration and geospatial data. Engineers work alongside mechanical and electrical engineers and with manufacturing and test technicians, so the code has to run on real machines.
This guide is for candidates applying for a Software Engineer position at Carnegie Robotics. It covers the three reported stages, the ten questions candidates report (transformation matrices, profiling, C++ and Python, GDB and valgrind, geospatial coordinates, and five project-history prompts), the reported requirements (C++, Python, 3D geometry, kinematics, autonomous-systems experience) and a week of practice built from them.
Candidates report three stages over roughly three to five weeks: a Recruiter Screening Call, Technical Interviews that often include live coding, and an Onsite/Virtual Interview with several engineers, a presentation on previous work and deep technical discussions.
Focus areas
- Coding interviews (problem solving)
- Data structures & algorithms
- C++
Overview
The work candidates describe sits between application code and hardware. Reported examples are infrastructure pieces (message serialization, process management, custom loggers that write to NVMe drives, OTA updates) next to perception-side work (point clouds, GPU kernels, geospatial data). The reported requirements put C++ and Python first, then 3D geometry and kinematics, then experience with autonomous systems or robotics. Rust, Go and CUDA are listed as extras, and candidates mention that the role requires US Person status and comfort working with little supervision.
The ten reported questions split into two groups. Five are technical and domain questions: how you handle transformation matrices and kinematic chains, how you use perf or Callgrind to speed up software, what you have done with C++ and Python in robotics or computer vision, a low-level failure you debugged with GDB or valgrind, and how you keep geospatial data and coordinate systems accurate. Five are project-history questions: working with physical hardware, taking feedback in code review or on GitHub pull requests, working autonomously on a hard problem, why robotics meets industry interests you, and prioritising several R&D projects at once. The topic mix candidates describe adds coding problems, data structures and algorithms, C++ and software-engineering concepts.
Preparation therefore has three strands. Second, a small set of domain answers you can back with working code: compose and invert rigid transforms, read a profile, narrate a debugger session, name the frame and datum of every coordinate. Third, four or five project stories that survive follow-ups, because candidates also report a presentation on previous work and say that GitHub repositories and project documentation may be looked at, so the projects you present need to be ones you can explain down to the trade-offs.
The Carnegie Robotics interview process
Candidates describe three stages. The sequence below is what they report rather than a published process, and the reported domain and project questions are not tied to a particular stage.
Recruiter Screening Call
Candidates report an initial call with a recruiter about your background and fit for the role. No technical content is reported for this stage, so treat it as the place where your story is set: which projects you lead with, why robotics, and whether you meet the listed requirements. Candidates describe a US Person requirement for the role and a preference for people comfortable with low supervision, so be ready to speak to both plainly.
What to demonstrate
- Whether your background lines up with the reported requirements: C++, Python, 3D geometry, kinematics, autonomous systems or robotics
- Your reasons for wanting robotics work specifically
- Eligibility and logistics that the recruiting team has to confirm
How to prepare
- Write a short walk through your CV that leads with the project closest to autonomous systems, hardware or geospatial data, and name your exact contribution to it.
- Prepare a specific answer to why robotics and real-world industrial use interests you, drawn from something you built or studied rather than from the company's description.
- Confirm your work-authorization status and availability before the call, and ask the recruiter what the later stages involve.
Technical Interviews
Candidates report one or more technical interviews, often with live coding to establish baseline skills. The topic mix they describe is coding problems, data structures and algorithms, C++ and software-engineering concepts. The reported domain questions (transformations, profiling, geospatial accuracy, debugging) are not tied to a stage, so prepare them here as well as for the onsite. Plan to type working C++ while talking through your reasoning.
What to demonstrate
- Working code on a live problem, which is what a live coding stage tests by its format
- Data-structure and algorithm choices and their complexity
- C++ fluency: ownership, containers, iterators, const-correctness
- The reasoning you say out loud while you solve
How to prepare
- Solve problems in C++ in a plain editor without autocomplete: the interval and peak-concurrency problem, the top-k-in-a-window problem and the stream-merging problem in the practice set are good material.
- For each problem, state the complexity, name two edge cases (empty input, ties, duplicates, out-of-order data) and test them before you say you are done.
- Rehearse the transform-composition and profiling answers with a short code example for each, so you can write them rather than only describe them.
- Keep a Python version of each solution ready in case you are asked to switch languages.
Onsite/Virtual Interview
Candidates describe a more extensive series, often day-long, onsite or virtual, with several engineers, a presentation on previous work and deep technical discussions. The reported evaluation areas include hardware-software integration (IPC and middleware, memory and CPU limits on embedded systems, debugging with GDB on deployed systems) and geometry (coordinate systems, transformations, point-cloud basics, custom kernels and profiling). Prepare a presentation and rehearse the deep-dive questions out loud.
What to demonstrate
- How clearly you present previous work and the decisions behind it
- Depth on hardware-software integration: IPC, middleware, resource limits, debugging in deployed environments
- Comfort with 3D geometry, coordinate systems and point-cloud basics
- Consistency across several conversations with different engineers
How to prepare
- Outline a presentation of one project: problem, architecture, the two decisions you would defend and the one you would change, and the measured result.
- Choose a project that touched hardware or a deployed system, and prepare the debugging story with the exact tools and the root cause.
- Practise explaining IPC choices (shared memory versus sockets, serialization cost, message ordering) and a memory or CPU budget you had to meet.
- Update your GitHub profile and project documentation so that the repositories you mention have a readable README and build instructions.
Where candidates lose points
Describing transformations without naming conventions
When you explain how you handle transformation matrices and kinematic chains, state the frame convention (parent-from-child), multiplication order, homogeneous 4x4 form, how you store rotation (matrix or quaternion), and how you keep it valid (renormalise, check orthonormality). Be ready to write a compose-and-invert function and a test with a known answer.
Calling something slow without a profile
Do not guess at hotspots. Say you build with optimisation on and debug symbols, record with perf (perf record -g, then report) to find hot functions, use Callgrind for instruction and cache behaviour on a small deterministic input, change one thing, and measure again. Keep before and after numbers from a real project.
A debugging story that is only a tool name
For the GDB and valgrind story, walk through symptom, reproduction, hypothesis, evidence, root cause and fix. Name the specific error (use-after-free, invalid read, race) and how you confirmed the fix, rather than saying you stepped through it.
Coding in silence or skipping tests on the live problem
Candidates report live coding in the technical stage and talk about explaining your reasoning. State the approach and complexity before typing, write C++ you can compile in your head, and run through empty, duplicate and out-of-order inputs at the end.
Presenting previous work you cannot defend in detail
Pick a project where you made the design decisions. Prepare the alternatives you rejected, the measured result, and what failed. Check that the repository, README and build steps for anything you mention are current, since candidates report that interviewers may look at them.
Questions & practice
Identify the heaviest tenants in a five-minute window under memory pressure
Medium · top-k · heavy hitters · streaming
The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.
Approach
- Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
- Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
- State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
- Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.
Follow-up
- The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
- Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Merge partitioned event streams into one ordered feed with bounded lateness
Hard · k-way merge · watermarks · out-of-order streams
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
Explain how you handle transformation matrices and kinematic chains in your code.
Medium · Technical and Domain Knowledge
Approach
- Represent every pose as a 4x4 homogeneous transform: a 3x3 rotation R (orthonormal, det +1) plus a translation t. Name each one T_a_b, meaning it maps points expressed in frame b into frame a, so composition reads T_a_c = T_a_b * T_b_c and the inner frame labels must cancel. That naming rule catches most frame-order bugs at review time.
- Invert a rigid transform in closed form as [R^T | -R^T t] instead of calling a general matrix inverse. It is cheaper and exact, and it keeps the rotation orthonormal. In C++ I use a typed rigid-transform class such as Eigen::Isometry3d so a pose cannot be mixed up with an arbitrary 4x4 matrix.
- Forward kinematics for a serial chain is the ordered product of each link's fixed offset and its joint motion (a rotation about the joint axis for a revolute joint, a translation for a prismatic one), from base to end effector, so it costs O(n) for n joints. Cache the partial products when several frames along the chain are queried.
- Choose a rotation representation for the job: quaternions or rotation matrices for computation and storage, Euler angles only at the UI boundary because of gimbal lock and order ambiguity (XYZ vs ZYX). After many accumulated products, renormalise quaternions or re-orthonormalise R so floating-point drift does not shear the pose.
- Edge cases to state: row- vs column-vector convention, active vs passive rotations, degrees vs radians, units per link, and transforms sampled at different timestamps. Unit tests: T * T^-1 equals identity within tolerance, and hand-computed poses for simple joint angles such as all zeros and single 90-degree moves.
Follow-up
- How would you compute inverse kinematics for a 6-DOF arm, and what happens near a singularity of the Jacobian?
- How do you interpolate smoothly between two poses (for example slerp on the rotation plus linear interpolation on the translation)?
- Two sensors publish transforms at different rates; how do you look up the pose of one in the other's frame at a given timestamp?
How do you optimize software performance using profiling tools like perf or Callgrind?
Medium · Technical and Domain Knowledge
Approach
- Measure before changing anything: a reproducible benchmark on fixed input, an optimised build that keeps symbols (-O2 -g), and a recorded baseline of wall time and throughput. Decide which metric matters (latency per frame, CPU share, throughput) so the result can be judged against it.
- Start with perf stat for hardware counters (cycles, instructions, IPC, cache-misses, branch-misses) to tell compute-bound from memory-bound code. Then run perf record -g and perf report, or a flame graph, to find the hot call paths. Use --call-graph dwarf, or build with -fno-omit-frame-pointer, when stacks come out broken. Sampling has low overhead, so it can run on the real workload.
- Use Callgrind (valgrind --tool=callgrind, viewed in callgrind_annotate or KCachegrind) when you need exact per-function instruction counts and inclusive or exclusive call costs. It runs the program many times slower and counts instructions instead of measuring time, so it misses I/O waits and lock contention. Its cache figures come from a simulation (--cache-sim=yes).
- Typical fixes, in order of payoff: a better algorithm or data structure, then contiguous memory layout and fewer cache misses (struct-of-arrays, std::vector over linked nodes), then removing allocations and copies in hot loops (reserve, preallocated buffers, const references, moves), then vectorisation. Re-run the same benchmark after each change and keep only changes that show a measured gain.
- Check off-CPU time separately: on-CPU sampling cannot see a thread blocked on a mutex, a disk write or a socket, so use scheduler or off-CPU tracing (for example perf sched or perf trace) when wall time is high but CPU use is low.
Follow-up
- perf points at a function that is inlined everywhere or shows unknown frames; how do you get a usable profile?
- How would you profile on a resource-limited embedded target where Valgrind is too slow to run in real time?
- How do you stop a performance gain from regressing later, for example with a benchmark gate in CI?
How do you approach geospatial data and coordinate systems to ensure system accuracy?
Medium · Technical and Domain Knowledge
Approach
- Name the frames explicitly: geodetic WGS84 (latitude, longitude, ellipsoidal height), ECEF, a local tangent plane (ENU or NED) anchored at a fixed origin, and projected grids such as UTM. Record the datum and EPSG code with every dataset, and watch axis order: GeoJSON stores longitude before latitude, while many APIs take latitude first.
- For local robot work, convert geodetic to ECEF and then to ENU about one origin, using a tested library such as PROJ or GeographicLib rather than handwritten formulas. A flat-earth approximation is fine only over short distances, because its error grows with distance from the origin. UTM also breaks at zone boundaries.
- Keep precision in mind: absolute ECEF values are around 6.4e6 m, and a 32-bit float spaces values about 0.5 m apart at that magnitude. So store absolute coordinates as doubles, and use floats only for offsets from a local origin, for example in point clouds sent to a GPU.
- Separate the height references. Ellipsoidal height from GNSS and orthometric height above a geoid model differ by tens of metres depending on location. Also align time: GPS time runs ahead of UTC by the accumulated leap seconds (18 s at present), so sensor timestamps must share one clock before fusing positions.
- Check accuracy against ground truth: round-trip conversion tests (geodetic to ENU and back within millimetres), surveyed control points, and residuals between GNSS fixes and the fused estimate. Log the covariance or fix quality so degraded fixes can be filtered out.
Follow-up
- How would you fuse GNSS with IMU and wheel odometry, and how do you handle GNSS dropouts (for example with an extended Kalman filter)?
- The vehicle drives across a UTM zone boundary mid-mission; what breaks, and how do you design around it?
- A base map uses a different datum from your GNSS receiver (for example NAD83 vs WGS84); how do you detect and correct the offset?
Edge instances grow 400 MB per hour until the nightly restart
Hard · memory leak · heap analysis · metric cardinality
Edge API instances start at 700 MB resident and grow about 400 MB/hour; a nightly rolling restart has hidden it for weeks. Growth continues unchanged when request rate halves overnight, p99 degrades in the last hours before an instance is recycled, and heap used immediately after a forced full GC rises monotonically. The service holds no product state. Name the discriminating measurement that separates the plausible causes, give the most likely cause, and give the fix and how you would verify it.
Approach
- Separate resident memory from live heap first, because they fail differently. Resident size can grow from fragmentation, native buffers or thread stacks while the heap is flat; heap used after a full GC rising monotonically is the measurement that says objects are reachable and not being released. You already have it, so this is retention, not fragmentation, and that closes off half the candidate list.
- Use the rate's independence from traffic as the discriminator. Growth that continues at half the request rate rules out per-request objects that are merely slow to collect and points at a structure that grows with distinct values observed rather than with call volume. Write the candidates that have that property: a metrics registry keyed on a high-cardinality label, an unevicted cache, an interner, a per-key lock map.
- Take two heap snapshots an hour apart and diff by retained size, reading the dominator tree, not by allocation count or instance count. Expect one root holding a map with millions of entries, then follow the reference chain to the code that inserts and never removes. Allocation profilers point at churn, which is the wrong signal here.
- The candidate that fits this service is an observability label carrying an identifier, such as a request path recorded before templating so that /v1/resources/48213 becomes its own metric series. That grows with distinct ids seen, is independent of rate, and explains the late p99 degradation, since GC cost rises with the size of the live set.
Follow-up
- Post-GC heap is now flat but resident size still creeps. What are you looking at, and does it matter?
- How would you have detected this before an OOM, given the nightly restart masked the trend?
Suggested practice checklist
The week follows the three reported stages: coding and C++ first, then the geometry, profiling and debugging answers, then project stories and a presentation rehearsal.
Day 1 — Map the loop and inventory your projects
- List the three reported stages and write what you expect to be asked at each, using the reported topics: coding, data structures and algorithms, C++, hardware-software integration, geometry.
- List every project in your history that involved hardware, a deployed system, geometry or a profiler, and mark the two you could present for a long deep-dive.
- Write a short pitch for why robotics interests you, using a real example, and note your work-authorization status for the recruiter call.
Deliverable: A one-page loop map, a ranked project list with two chosen, and the written robotics motivation pitch.
Day 2 — Live C++ on intervals and top-k
- In a plain editor with no autocomplete, solve the overlapping job attempts and peak-concurrency problem (sort and sweep, or a heap) in C++.
- Solve the heaviest-tenants-in-a-window problem, choosing between a hash map with a heap and a bounded structure, and state its complexity.
- Say each approach aloud before writing, then list three edge cases and test them.
Deliverable: Two compiling C++ solutions, each with complexity stated and a list of tested edge cases.
Day 3 — Streams, ordering and profiling
- Solve the problem of merging partitioned event streams into one ordered feed with bounded lateness, using a k-way merge with a heap and a watermark.
- Profile a small C++ program of your own with perf record and perf report, then with Callgrind, and fix one hotspot.
- Write a before-and-after note with numbers, and rehearse the answer to the profiling question using it.
Deliverable: A working stream-merge solution and a profiling note with measured before and after numbers.
Day 4 — Transforms and geospatial coordinates
- Write C++ (or Python) that composes, inverts and applies rigid transforms along a chain of joints, with a test whose answer you can check by hand.
- Write a one-page reference on coordinate frames: which frame each input uses, which datum and projection geospatial data uses, and where conversions happen in your code.
- Answer the transformation question and the geospatial-accuracy question aloud, using your code and note.
Deliverable: A tested transform-chain implementation, a coordinate-frame reference page and two spoken answers.
Day 5 — Low-level debugging and the C++ and Python story
- Reproduce a heap overflow or use-after-free in a small program, then find it with GDB (backtrace, watchpoint) and valgrind memcheck.
- Work through the practice debugging scenario in which instances grow by hundreds of megabytes per hour, and write the investigation order you would follow.
- Write your C++ and Python robotics or vision story, including the boundary between languages and one measured result.
Deliverable: A written debugging narrative from symptom to fix, an investigation order for the memory-growth scenario, and the C++ and Python story.
Day 6 — Project-history stories
- Write the hardware project story, the code-review feedback story and the autonomy story, each with situation, your decision, result and a lesson.
- Write the prioritisation answer around a real period when several projects competed for you.
- Say all four aloud, then ask a friend for follow-up questions and answer them.
Deliverable: Four rehearsed stories, each with notes on the follow-up questions you were asked.
Day 7 — Presentation and full dry run
- Build the outline for your presentation of previous work: problem, architecture, two defended decisions, one regret, measured result.
- Present it aloud to someone, then take questions on trade-offs and failures.
- Clean up the README and build instructions of the repositories you will mention, then run one coding problem, one domain answer and one story back to back.
Deliverable: A presentation outline presented once, cleaned-up repositories, and a completed mock run.
Hardware, autonomy and project-history stories
The reported project-history questions cover hardware work, code review, working autonomously, motivation for robotics and prioritising R&D work. The debugging question and the C++ and Python experience question are also asked as past-experience prompts. Candidates describe a role with low supervision and cross-discipline collaboration, so choose stories where you made the decisions, worked with people outside software, and can give a measured result.
What is your experience with C++ and Python in the context of robotics or computer vision?
Medium · Technical and Domain Knowledge
Approach
- Open with the split, not a list of languages. Say which parts you wrote in C++ (latency-sensitive loops, drivers, serialization, memory-aware code) and which in Python (tooling, offline analysis, test harnesses, prototypes), and why the boundary sat where it did.
- Show concrete C++ practice: RAII and smart pointers for ownership, no allocation in hot loops, move semantics for large buffers, and a linear-algebra library for transform maths if you used one.
- Walk through one vision or robotics pipeline end to end: input (images or point clouds), processing steps, output, and one measured result such as frame rate, latency or error. If you used PyTorch, say how the model reached C++ (for example a TorchScript export loaded with libtorch).
- Cover the bridge between the languages: pybind11 or a similar binding, who owns the memory, and how you avoided copying large arrays between Python and C++.
- Be honest about depth. If one language is weaker, say what you would study next; claiming equal mastery of both is the fastest way to be exposed by a follow-up.
Follow-up
- Where did the Python and C++ boundary cost you performance, and how did you measure it?
- How do you pass a large image or point cloud between the two without copying it?
- What would you refuse to write in Python on a robot, and why?
Can you describe a time you had to debug a failure in a low-level system using GDB or valgrind?
Medium · Technical and Domain Knowledge
Approach
- Choose a failure with a non-obvious symptom: a crash that only appeared in the deployed build, intermittent memory corruption, a hang or a slow leak. State the symptom and how you reproduced it, for example by enabling core dumps or running gdbserver on the target.
- Narrate the sequence as hypotheses: take a backtrace (bt, thread apply all bt), inspect frames and variables, set a watchpoint on the corrupted field, and check build flags (a debug-info build changes timing and layout).
- For memory errors, explain what Valgrind's memcheck reported (invalid read or write, use of uninitialised value with --track-origins=yes, leaks with --leak-check=full), and why you might switch to AddressSanitizer or ThreadSanitizer when valgrind's slowdown hides a timing bug. Helgrind or ThreadSanitizer covers races.
- Name the root cause precisely (use-after-free, out-of-bounds index, race on shared state, uninitialised member, callback outliving its object), the fix, and the regression test or stress loop that showed it stayed fixed.
- Avoid ending with only the tool name. A story that says I ran GDB and found it shows no reasoning; the story is the chain from symptom to hypothesis to evidence.
Follow-up
- Why could you not reproduce it under a debugger, and how did you deal with the timing change?
- What would you have done if you could not attach a debugger on the target?
- What did you change so that this class of bug would be caught earlier?
Tell me about a project where your code had to interact with physical hardware. What were the challenges?
Medium · Behavioral and Project History
Approach
- Pick a project where a sensor, actuator, board or test rig was involved and you wrote or changed code that touched it. State what the hardware did and what your code was responsible for in two sentences.
- Name the challenges specifically: timestamps and clock drift between devices, driver or firmware quirks, noisy readings, units and frame conventions, behaviour that differed between simulation and the real device, limited access to the machine.
- Describe how you made progress without constant hardware access: logging and replaying recorded data, simulators or mocks, hardware-in-the-loop tests, and what you checked first when behaviour looked wrong.
- Include a moment where the hardware overruled your assumptions and what you changed, plus a safety or recovery measure (watchdog, limits, safe default) if there was one.
- Close with the result and what you would do differently. A story told entirely in software terms misses the point of the question.
Follow-up
- How did you separate a software fault from a hardware or wiring fault?
- How did you test the code when the device was not on your desk?
- Who on the mechanical or electrical side did you work with, and what did you need from them?
How do you handle feedback on your code during peer reviews or GitHub pull requests?
Medium · Behavioral and Project History
Approach
- Use two stories in one answer: a time you received a review comment you disagreed with, and your general habits as a reviewer and author. A single story with no pushback sounds untested.
- Describe the comment concretely (naming, ownership, an unsafe cast, missing test, a performance claim) and what you did: ran a benchmark or wrote a test to settle it, or accepted the point and changed the code.
- Show how you work on a GitHub pull request: small reviewable commits, a description that states intent and how it was tested, replies to every thread, and a follow-up when you defer something.
- Show the other side: you review others by asking questions, pointing at the line and the reason, and separating must-fix from taste.
- The mistake that sinks this answer is defensiveness or the claim that you accept all feedback. Show a case where evidence changed your position, or where you held yours with data.
Follow-up
- Tell me about a review comment that turned out to be wrong, and how you handled it.
- How do you keep a long-running pull request from becoming hard to review?
- What do you check first when you review someone else's C++ change?
Describe a situation where you had to work autonomously to solve a complex technical problem.
Medium · Behavioral and Project History
Approach
- Choose a problem that had no clear owner or specification and where nobody was available to hand you the answer. State what was unclear and what success looked like once you defined it.
- Show how you broke it down: a reproduction or minimal experiment first, a written plan with milestones, and decisions you recorded as you made them.
- Show the limits of autonomy. Name the point at which you asked for input or told a teammate what you were doing, and why you chose that moment; working alone is not the same as working silently.
- Give the result with a measure, and what you left behind (documentation, a test, a script) so the next person was not dependent on you.
- Avoid the hero story in which you disappeared for weeks and came back with a surprise; avoid the opposite, where you describe waiting for direction.
Follow-up
- How did you decide when you were stuck enough to ask for help?
- What did you do when you realised your first approach was wrong?
- How did you keep the rest of the team informed while you worked alone?
What interests you about the intersection of robotics and real-world industrial applications?
Medium · Behavioral and Project History
Approach
- Tie your interest to something you have done or studied: a project where code controlled or measured something physical, a course in perception or control, or a hobby build. Name it and say what you learned that pure software did not teach you.
- Say what draws you about the constraints: sensor noise, limited compute and memory, behaviour in the field where you cannot easily attach a debugger, and collaboration with mechanical and electrical engineers. Candidates describe exactly these conditions for this role, so refer to them as the work you expect.
- Connect it to the reported application areas, defense to agriculture, with a specific thought about one of them. Have a straight answer ready about how you feel about defense-related work, since candidates describe it as one of the fields and as a nice-to-have background.
- Finish with what you would contribute: for example the C++ and geometry skills you would bring, or the logging and debugging habits you built on past projects.
- Avoid a generic statement such as I love robots. Any specific, checkable detail beats enthusiasm.
Follow-up
- Which part of the robotics stack would you least want to work on, and why?
- What have you built outside work or study that connects to this?
- What would you want to learn in your first months?
How do you prioritize tasks when working on multiple R&D projects simultaneously?
Medium · Behavioral and Project History
Approach
- Start with a concrete situation: several R&D efforts with different owners, deadlines or unknowns running at once. Name how many and what each was, without exaggerating.
- State the method you actually use: list everything, then rank by impact, deadline, dependencies and cost of delay, and give extra weight to anything that needs scarce hardware or test-rig time.
- Treat research work differently from delivery work: put an explicit stopping rule on exploratory tasks (a question to answer or a result to reach) so they do not consume the week.
- Explain how you communicated the trade-offs: what you dropped or delayed, who agreed, and how you made the new plan visible. Prioritisation decided silently is a weak answer.
- Close with the outcome and one thing you changed afterwards, such as a weekly re-rank or a shared task board.
Follow-up
- Tell me about a priority call you got wrong and how you recovered.
- How do you handle a request that pushes aside work you consider more important?
- How do you protect time for deep debugging or experimentation?