Interview guides · Apollotech Msi
Apollotech Msi Software Engineer Interview Guide
Apollotech Msi Software Engineer interview guide: reported rounds, C++, ROS2 and Nvidia Jetson questions with worked guidance and a 7-day plan.
The 60-second brief
Apollotech Msi hires Software Engineers to write software for defense systems, autonomous tactical robots and radar platforms. Candidates describe embedded targets such as the Nvidia Jetson, where machine learning models run on the device itself, and software that reads sensor feeds and runs real-time control loops. The listed stack is C++ and ROS2, with Python for scripting and for analysing field test data, GitLab for source control and automated testing, and Unreal Engine for simulation in some work. The engineer works next to mechanical, robotics and radar specialists and follows software from design through integration on physical hardware. The role is based mainly at Picatinny Arsenal, NJ, and Lower Longwood, NJ is also listed.
This guide is for Software Engineer candidates preparing for the Apollotech Msi loop. It covers the three reported rounds, 18 reported questions in four groups (embedded C++, ROS2 architecture, edge AI with Python and Kotlin, and behavioral or collaborative scenarios), and the requirements candidates describe: a bachelor's degree or higher in computer science, software engineering, robotics, electrical engineering or a related field, US citizenship, and the ability to obtain and keep a DoD Secret clearance. C++ and Python, ROS2, Jetson familiarity and GitLab are the listed must-haves. Unreal Engine, Kotlin and radar, DSP or RF experience are listed as nice-to-haves.
Candidates report three rounds over roughly 3-5 weeks: Initial Alignment, Technical Evaluation and Final Decision Review. The first covers your background, goals and clearance eligibility, and the second covers hands-on coding, systems design and problem solving.
Focus areas
- C++
- Python
- Embedded Systems Software Development
Overview
The work described for this role sits where software meets physical machines. A typical week mixes performance-sensitive C++ for an embedded target, ROS2 networking between autonomous subsystems, Python scripts that analyse field test data, GitLab merge requests with automated tests, and trips to test sites for integration on real platforms. Candidates report 10% to 20% travel for that field work. For preparation this means your answers should name the constraint behind every choice: how much memory the target has, which thread touches which data, what happens to the robot when a message stops arriving.
The 18 reported questions fall into four groups. Five are about embedded C++: stack versus heap, race conditions, the rule of three/five/zero, smart pointers and cache misses in a hot loop. Five are about ROS2: ROS versus ROS2 middleware, DDS Quality of Service, debugging publisher-to-subscriber latency, a TF2 pipeline for a multi-joint arm, and lifecycle nodes. Four cover edge AI and language choice: TensorRT optimization on a Jetson, Python versus Kotlin, prototyping in Python before rewriting in C++, and asynchronous streams in Kotlin. Four are behavioral: debugging with a hardware engineer, GitLab review habits, field or client experience, and shifting requirements. Candidates also describe scenarios such as ingesting camera feeds on a Jetson without dropping frames, chasing a sporadic segmentation fault in threaded C++, halting a robot when a sensor node goes silent, and fitting a PyTorch model into a 15-watt power budget.
Which of these questions appears in which round is not reported, so treat the four groups as one pool and prepare each to the point where you can answer aloud without notes. The strongest preparation is hands-on: write the RAII wrapper and the lock-free queue yourself, run a ROS2 publisher and subscriber with mismatched QoS to see what fails, and export a small model through ONNX to TensorRT if you have a Jetson or any NVIDIA GPU.
Two process details matter before any technical work. Candidates report that US citizenship is strictly required and that the resume should state your citizenship status or current clearance level. They also report being told to keep every example to open-source, academic or unclassified commercial projects, so choose your project stories with that boundary in mind and rehearse them in a form that never touches prior classified work.
The Apollotech Msi interview process
Candidates describe three stages over about 3-5 weeks, with the technical content concentrated in the second. Which reported question is asked in which stage is not reported, so prepare every group.
Initial Alignment
Candidates describe Initial Alignment as a conversation about how your background, career goals and clearance eligibility fit what the team needs. Treat it as the stage where your resume story has to hold together: the projects you pick should show embedded, robotics or edge work, and your citizenship and clearance position should be stated plainly. Nothing in the reports suggests a coding exercise here, but be ready to go one level deeper on any technology you list.
What to demonstrate
- Fit between your background and the team's needs
- How your career goals line up with this kind of work
- Clearance eligibility, including US citizenship and the ability to obtain a DoD Secret clearance
How to prepare
- Write a short walkthrough of two or three unclassified projects that touched hardware, sensors, a robot or an embedded board, naming your own contribution in each.
- Put your citizenship status or current clearance level on the resume and be ready to state it in one sentence.
- Learn the role's listed stack (C++, Python, ROS2, Nvidia Jetson, GitLab) and the 10% to 20% travel, and decide how you would explain your comfort with field testing.
- Prepare an honest answer on why defense robotics and edge systems suit you, using open-source or academic examples.
Technical Evaluation
Technical Evaluation is reported as deep dives into hands-on coding, systems design and problem solving. The reported question groups give the likely material: modern C++ memory and threading, ROS2 architecture, and edge AI deployment. Approach it by writing code that is correct first and cheap second, and by stating CPU, memory and bandwidth limits out loud as you design. Candidates also describe design scenarios, such as a mobile robot that navigates autonomously, takes radar input and reports state to a base station, so practise drawing a node graph with its message flows.
What to demonstrate
- Hands-on coding that compiles, runs and handles edge cases
- Systems design: splitting an embedded or robotic system into modules with clear interfaces
- Problem solving and a systematic debugging process
How to prepare
- Write from scratch a RAII class that follows the rule of five, a thread-safe bounded queue, and a loop rewritten for cache-friendly access, then explain each choice aloud.
- Draw a ROS2 graph for an autonomous mobile robot with a radar input and a base-station link, labelling topics, services or actions, and the QoS profile of each stream.
- Rehearse two debugging narratives in order: a sporadic segmentation fault in a multi-threaded C++ program, and high latency between a publisher and a subscriber.
- Work the TensorRT-on-Jetson question as a checklist from baseline measurement to precision, batching and power mode.
Final Decision Review
Final Decision Review is described as a comprehensive review of your engineering capabilities before the decision. Nothing further is reported about its format, so prepare as if any earlier topic can return and as if you will need to defend decisions from your own projects. Use it to tighten the weakest of the four question groups and to have each behavioral story ready in a clean beginning, middle and result.
What to demonstrate
- Your engineering capability reviewed as a whole across the earlier topics
- The depth of your account of past projects and the trade-offs behind them
How to prepare
- Pick your weakest group (embedded C++, ROS2, edge AI or behavioral) after the first two rounds and redo its questions aloud, without notes.
- Prepare four STAR stories: debugging with a hardware engineer, a GitLab workflow in a small team, field or client work, and ambiguous requirements under a deadline.
- Write a list of design decisions from your own projects with the alternative you rejected and the measurement that justified the choice.
- Prepare a few questions about the hardware platforms, test events and tooling you would work with.
Where candidates lose points
Calling smart pointers "safer" without naming their costs on a small target
Be able to say that unique_ptr adds essentially no overhead over a raw owning pointer, while shared_ptr carries a control block and atomic reference counts, that make_shared saves an allocation, and that cycles need weak_ptr. Then say when you would avoid both: a fixed pool or static storage after startup.
Answering the race-condition question with "use a mutex" and nothing else
Name the shared state and who owns it, then distinguish a data race from a deadlock and from priority inversion on a real-time thread. Describe the alternatives: minimizing sharing, message passing, atomics with explicit memory ordering, and a single-producer single-consumer ring buffer. Run your own example under ThreadSanitizer before the interview.
Treating ROS2 QoS as default settings instead of a per-stream decision
Match the profile to the data: sensor streams usually want best-effort delivery with a shallow history, while control commands want reliable delivery. Know that a reliable subscriber will not connect to a best-effort publisher, and that deadline or liveliness settings can detect a silent sensor node, which is the basis of the halt-on-silence scenario.
Stopping the Jetson optimization answer at "convert it with TensorRT"
Give a sequence: measure a baseline for latency, throughput and power; export to ONNX; build a TensorRT engine in FP16, then INT8 with calibration data; check accuracy after each step; tune batching and streams; and pick a power mode that fits the budget, such as the 15-watt scenario. Report the accuracy-versus-speed trade-off.
Behavioral stories where the hardware, field or client part is vague, or where classified detail slips in
Prepare the four stories (hardware debugging, GitLab workflow, field or client work, ambiguous requirements) with named steps, your own role and a measured result, using open-source, academic or unclassified examples. Rehearse them aloud so no restricted project detail appears under pressure.
Questions & practice
Explain the difference between stack and heap memory allocation. When would you strictly avoid heap allocation
Medium · Embedded Systems & C++ Programming
Explain the difference between stack and heap memory allocation. When would you strictly avoid heap allocation in an embedded system?
Approach
- Define both precisely: stack storage is automatic, LIFO and per thread; allocating is a stack-pointer adjustment and it is released at scope exit. Heap storage comes from a general allocator (new/malloc), lives until explicitly freed, and its cost is not bounded: free-list searches, an allocator lock, sometimes a call into the OS.
- Name the stack's own risks: a fixed size per thread or RTOS task, overflow from large local arrays or deep recursion with no bounds check, and dangling pointers when a function returns the address of a local. Mention stack painting or a high-water mark, plus -fstack-usage, to size task stacks.
- Say where I would refuse heap allocation outright: inside interrupt handlers (malloc is not reentrant), inside a fixed-rate control loop where timing must be bounded, and after start-up in long-running firmware, where fragmentation can make an allocation fail after days of uptime. MISRA C:2012 Rule 21.3 and MISRA C++:2008 Rule 18-4-1 ban dynamic allocation for this reason.
- Give the replacement pattern: allocate everything during initialisation, then use std::array, fixed-capacity containers, fixed-block object pools, vector::reserve up front, or a std::pmr::monotonic_buffer_resource over a static buffer.
- List the hidden allocations to watch for: vector growth, std::string beyond its small-string buffer, std::function captures too large for its inline storage, shared_ptr control blocks, and thrown exceptions.
Follow-up
- How would you prove that a control loop performs no heap allocation after start-up?
- Why does a fixed-block pool avoid fragmentation, and what does it cost you in memory?
- How would you size the stack for each task on an RTOS?
How do you manage concurrency and prevent race conditions in a multi-threaded C++ application?
Medium · Embedded Systems & C++ Programming
Approach
- Start by listing the shared mutable state and who owns it. The cheapest race to fix is one designed out: confine data to one thread and hand work over through a queue instead of sharing it.
- For the state that must be shared, use std::mutex through RAII (std::lock_guard, std::unique_lock). Lock several mutexes with std::scoped_lock or a fixed global lock order to avoid deadlock, and keep critical sections short, with no I/O or blocking calls inside them.
- For producer-consumer handoff, build a bounded queue with a mutex plus std::condition_variable, and always wait with a predicate so spurious wakeups and missed notifications are handled. A bounded queue makes back-pressure explicit instead of letting memory grow.
- Use std::atomic for single counters and flags, and explain memory ordering: a release store paired with an acquire load publishes the data written before it. A data race on a non-atomic variable is undefined behaviour, not only a stale read.
- Cover real-time concerns and tooling: priority inversion (fixed with priority-inheritance mutexes, PTHREAD_PRIO_INHERIT), a lock-free single-producer single-consumer ring buffer for the control thread, and ThreadSanitizer (-fsanitize=thread) in CI to catch races that tests miss.
Follow-up
- Write a thread-safe bounded queue and explain why the wait needs a predicate.
- What is priority inversion, and how would you prevent it between a sensor thread and a logging thread?
- Which memory orders does a single-producer single-consumer ring buffer need, and why are they enough?
What are smart pointers in C++11 and later? How do std::unique_ptr and std::shared_ptr impact performance
Medium · Embedded Systems & C++ Programming
What are smart pointers in C++11 and later? How do std::unique_ptr and std::shared_ptr impact performance on resource-constrained hardware?
Approach
- std::unique_ptr is exclusive, move-only ownership. With the default deleter it is the size of a raw pointer in mainstream implementations and dereferencing costs nothing extra; a stateful or function-pointer deleter adds size, and the deleter type is part of the unique_ptr type.
- std::shared_ptr holds two pointers (object and control block). The control block stores strong and weak counts, which are updated atomically on every copy and destruction: an atomic read-modify-write, plus cache-line traffic when several cores share the pointer.
- Allocation: std::make_shared puts object and control block in one allocation, but the memory is not returned until the last weak_ptr is gone. Constructing from a raw new means two allocations. On a constrained target, allocate_shared with a pool allocator, or avoid per-object shared ownership altogether.
- Usage rules that cut overhead: pass a plain reference or raw pointer to functions that do not take ownership, pass shared_ptr by const reference rather than by value when no copy is needed, and use weak_ptr to break cycles, which otherwise leak.
- Timing hazard: the object is destroyed on whichever thread drops the last reference, so a large destructor can run inside a real-time thread. The reference count is thread-safe; the pointee is not. In ROS2, publishing a std::unique_ptr message with intra-process communication enabled lets rclcpp hand the message over without copying it.
Follow-up
- Is std::shared_ptr thread-safe? Separate the control block, the pointer object and the pointee.
- When would you avoid make_shared?
- How would you pass camera frames between threads without allocating for every frame?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
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?
Describe the rule of three/five/zero in C++ and how it relates to resource management.
Medium · Embedded Systems & C++ Programming
Approach
- Rule of three: a class that needs a user-written destructor, copy constructor or copy assignment almost always needs all three, because it owns a resource (raw buffer, file descriptor, device handle). The compiler-generated copy is shallow, so two objects free the same resource.
- Rule of five (C++11): add the move constructor and move assignment. A user-declared destructor or copy operation suppresses the implicit move operations, so moves silently fall back to copies; declaring a move operation deletes the implicit copies.
- Rule of zero: give each resource its own single-purpose RAII wrapper (std::unique_ptr with a custom deleter, std::vector, a small FileDescriptor class) so the classes that use it declare none of the five and stay correct by default.
- Implementation details: mark move operations noexcept, because std::vector only moves elements during reallocation when the move cannot throw (move_if_noexcept); leave the moved-from object valid and destructible; handle self-assignment, for example with copy-and-swap, which also gives the strong exception guarantee.
- Embedded angle: a handle to a hardware resource (a DMA buffer, a serial port) should usually be move-only: delete the copy operations and default or write the moves, so ownership can transfer but never duplicate.
Follow-up
- A class has only a user-written destructor and is stored in a std::vector. What happens when the vector grows?
- Write a move-only class that owns a POSIX file descriptor.
- Why does a missing noexcept on a move constructor change performance?
How would you optimize a critical loop in C++ to minimize CPU cache misses?
Medium · Embedded Systems & C++ Programming
Approach
- Measure before changing anything: hardware counters with perf stat or perf record on Linux targets (cache-misses, cache-references), or cachegrind, so I know the loop is memory-bound and not compute-bound.
- Fix data layout first. Use contiguous storage (std::vector, std::array) instead of node-based containers (std::list, std::map) that chase pointers; switch from array-of-structs to struct-of-arrays when the loop touches only a few fields; split hot fields from cold ones; use smaller types where precision allows.
- Fix access order: C++ arrays are row-major, so the inner loop should walk the last index; interchange loops that stride across rows; tile or block matrix and image kernels so the working set fits in L1/L2. Cache lines are typically 64 bytes, so a stride that skips through them wastes most of every line it loads.
- For multi-threaded loops, remove false sharing: give per-thread counters or accumulators their own cache line (alignas(64), or std::hardware_destructive_interference_size where the compiler supports it).
- Help the prefetcher and the vectoriser: linear, predictable strides; __builtin_prefetch only for indirect access that the hardware cannot predict; invariant loads hoisted out of the loop. Then confirm with the same counters and with tail latency, not a single wall-clock run.
Follow-up
- Would you store a point cloud as an array of structs or as a struct of arrays for a voxel filter, and why?
- How would you demonstrate that false sharing is the cause of a slowdown?
- The miss count dropped but the loop is no faster. What do you check next?
What are the main differences between ROS and ROS2 regarding middleware and communication reliability?
Medium · Robotics & ROS2 Architecture
Approach
- ROS 1 relies on a central master (roscore) for name registration and discovery; after lookup, nodes connect peer to peer over TCPROS or UDPROS. The master is a single point of failure for discovery, and the only delivery controls are queue sizes and latching.
- ROS 2 runs on DDS/RTPS behind the rmw abstraction layer (Fast DDS, Cyclone DDS, Connext, among others). Discovery is decentralised and uses multicast by default, so there is no master, and ROS_DOMAIN_ID partitions separate systems on the same network.
- Reliability is set per endpoint through QoS: reliable or best effort, volatile or transient-local durability (which replaces latching), history depth, deadline, liveliness and lifespan. Incompatible publisher and subscriber QoS means no connection at all, which is a common migration bug.
- Other differences that matter on a robot: intra-process zero-copy and component composition (several nodes in one process), managed lifecycle nodes, DDS-Security through SROS2, and an executor model that is easier to make deterministic. ROS 1 Noetic reached end of life in May 2025.
- Trade-offs: multicast discovery can be unreliable over Wi-Fi or across subnets (fix with a discovery server or static peer lists), and reliable QoS over a lossy link can build a retransmission backlog that adds latency.
Follow-up
- How would you migrate a ROS 1 system incrementally, for example with ros1_bridge?
- A base station on Wi-Fi cannot discover the robot's nodes. What do you check and change?
- A subscriber requests reliable delivery and the publisher offers best effort. What happens?
Explain how the DDS (Data Distribution Service) layer in ROS2 handles Quality of Service (QoS) profiles.
Medium · Robotics & ROS2 Architecture
Approach
- QoS is matched by request versus offer: the publisher (DataWriter) offers a policy, the subscriber (DataReader) requests one, and they connect only if the offer is at least as strong. Reliable beats best effort, transient-local beats volatile, and an offered deadline period must be no longer than the requested one.
- History and reliability work together: KEEP_LAST(depth) keeps the newest N samples per instance, KEEP_ALL keeps everything up to the resource limits. With reliable delivery the writer retransmits from its history cache in response to RTPS HEARTBEAT and ACKNACK messages, so a slow reader on KEEP_LAST loses the oldest samples rather than blocking the writer forever.
- Know the ROS 2 presets: the default profile is reliable, volatile, keep last 10; the sensor-data profile (rclcpp::SensorDataQoS) is best effort, volatile, keep last 5. A latched-style topic such as a map or robot description uses transient_local so late joiners get the last value.
- Match profiles to the data: camera, lidar and radar streams use best effort with a small depth because a stale frame is worthless; control commands and state changes use reliable plus a deadline; configuration topics use transient-local.
- Use QoS events for monitoring: incompatible-QoS, deadline-missed and liveliness-lost callbacks can drive a watchdog. ros2 topic info --verbose shows each endpoint's QoS when debugging a silent topic.
Follow-up
- A subscriber to a sensor topic receives nothing although ros2 topic list shows the topic. What is your first check?
- How would deadline and liveliness QoS feed a watchdog that stops the platform when a sensor goes quiet?
- What does reliable delivery cost on a lossy radio link, and when would you switch to best effort?
Describe how you would implement a coordinate transformation (TF2) pipeline for a robotic arm with multiple jo
Medium · Robotics & ROS2 Architecture
Describe how you would implement a coordinate transformation (TF2) pipeline for a robotic arm with multiple joints.
Approach
- Describe the arm in URDF (or xacro): links, and joints with type, axis, origin and limits. robot_state_publisher reads the robot description and sensor_msgs/JointState and publishes moving joints on /tf and fixed joints on /tf_static. Joint states come from the encoder driver or a ros2_control joint_state_broadcaster.
- Keep the frame tree a tree: every frame has exactly one parent, for example world or map, then base_link, the chain of link frames, the tool frame. A camera mounted on the arm is attached with a static transform taken from calibration (static_transform_publisher or a launch file).
- Explain the maths: each joint contributes a homogeneous transform that depends on its joint value, and the end-effector pose is the product along the chain (forward kinematics). TF2 composes transforms along the path between any two frames through their common ancestor.
- On the consumer side, use a tf2_ros::Buffer with a TransformListener, call lookupTransform(target, source, stamp, timeout) with the sensor message's timestamp rather than the latest transform, catch LookupException and ExtrapolationException, and use tf2_ros::MessageFilter to hold messages until their transform is available.
- Follow REP-103 (right-handed axes, x forward, y left, z up, metres and radians) and REP-105 frame names, keep quaternions normalised, and stamp joint states from a consistent clock (simulated time in simulation).
Follow-up
- A wrist camera detects an object. How do you get its position in the base frame at the moment the image was captured?
- What causes extrapolation errors, and how do you fix them without simply using the latest transform?
- How would you handle a closed kinematic chain when TF2 requires a tree?
What is the role of lifecycle nodes in ROS2, and why are they important for safety-critical systems?
Medium · Robotics & ROS2 Architecture
Approach
- A managed (lifecycle) node, rclcpp_lifecycle::LifecycleNode, has primary states Unconfigured, Inactive, Active and Finalized, and transitions configure, activate, deactivate, cleanup and shutdown. Each transition calls a callback (on_configure, on_activate and so on) that returns SUCCESS, FAILURE or ERROR, and an error routes through on_error.
- Split the work by state: load parameters, allocate buffers and open hardware in on_configure; start publishing, timers and actuation only in on_activate; stop actuation in on_deactivate. That also keeps memory allocation out of the active loop.
- Lifecycle publishers deliver only while the node is Active, so an Inactive node cannot command hardware by accident.
- Safety value: an external manager (Nav2's lifecycle manager, or a custom one) brings nodes up in a known order and only activates actuators once sensors and estimators are confirmed Active. On a fault it can deactivate the chain into a known safe state instead of relying on a process crash.
- Pair lifecycle with liveness: Nav2's manager uses bond heartbeats to detect a node that died, and QoS deadline and liveliness events can trigger a transition to a halted state when a sensor stops publishing.
Follow-up
- What should happen if on_activate fails halfway through enabling a motor driver?
- Where would you put hardware initialisation, and where would you reset an emergency-stop latch?
- How would you design a watchdog that halts the platform when a required sensor node goes silent?
How do you optimize a deep learning model (e.g., TensorRT) for high-throughput, low-latency execution on an Nv
Medium · Edge AI & Python/Kotlin Development
How do you optimize a deep learning model (e.g., TensorRT) for high-throughput, low-latency execution on an Nvidia Jetson platform?
Approach
- Profile the whole pipeline first, not only the model: capture, pre-processing, host-to-device work, inference, post-processing. Benchmark the engine alone with trtexec, watch GPU, CPU and memory load with tegrastats, and fix the power mode (nvpmodel, jetson_clocks) so results are comparable.
- Export path: PyTorch to ONNX (pinned opset, static shapes or dynamic shapes with optimisation profiles), then build a TensorRT engine with trtexec or the builder API. Build on the target device, because an engine is tied to the GPU and TensorRT version it was built for. Unsupported ops need a graph rewrite or a plugin.
- Reduce precision: FP16 is the first step; INT8 needs calibration on a representative dataset (or quantisation-aware training) and an accuracy check on held-out data, with sensitive layers kept at higher precision. On modules that have DLA cores, eligible layers can be offloaded to the DLA to free the GPU.
- Trade throughput against latency explicitly: batching raises throughput but adds per-frame latency, so a real-time perception loop usually runs batch 1 and overlaps work with CUDA streams. On Jetson, CPU and GPU share DRAM, so mapped or zero-copy buffers avoid needless copies.
- Move pre-processing onto the GPU or a hardware block (CUDA, NPP, VPI), keep Python out of the per-frame path, and consider DeepStream for multi-camera pipelines. Drop frames deliberately instead of queueing them when the model cannot keep up.
Follow-up
- INT8 cost too much accuracy. What do you try before going back to FP16?
- How would you keep the pipeline within a fixed power budget on the device?
- Why does an engine built on a workstation fail or underperform on the Jetson?
What are the primary trade-offs between using Python and Kotlin for edge application development?
Medium · Edge AI & Python/Kotlin Development
Approach
- Python: quick iteration and the strongest ML and data ecosystem (NumPy, OpenCV, PyTorch), and rclpy is an officially supported ROS 2 client library. Costs: interpreter overhead, a larger memory footprint, the GIL limiting CPU-bound threads in standard CPython, and dependency packaging on ARM.
- Kotlin: static typing and null safety catch errors at compile time; it runs on the JVM (or Android) with JIT-compiled steady-state performance, and coroutines with Flow give structured concurrency for streams of events. Costs: JVM start-up time and memory, GC pauses, and a thinner ML and robotics library set.
- Integration: ROS 2's official client libraries are C++ and Python, and JVM bindings are community-maintained, so a Kotlin app often talks to the robot through a bridge (rosbridge/WebSocket, gRPC or a custom protocol) rather than joining the DDS graph directly.
- Decide by target and workload: Kotlin for an Android handheld or an operator UI; Python for data analysis, prototyping and ML glue on the Jetson; neither for hard real-time loops, which belong in C++.
- Deployment and maintenance: Kotlin ships as a single JAR or APK; Python needs an interpreter plus pinned dependencies, usually in a container. Mention team familiarity and how the code will be tested in CI.
Follow-up
- Which would you choose for an Android handheld that shows live robot telemetry, and how would it get the data?
- How would you work around the GIL for CPU-heavy work in Python?
- What would make you move a Kotlin or Python component into C++?
Describe a scenario where you would use Python for prototyping but rewrite the final implementation in C++.
Medium · Edge AI & Python/Kotlin Development
Approach
- Pick a concrete case, for example a tracking filter (a Kalman filter over radar or lidar detections) or a point-cloud pre-processing step: I prototype it in Python with NumPy against recorded rosbag data because tuning and plotting are fast.
- State the rewrite triggers: profiling shows per-element Python loops that cannot be vectorised, latency jitter from the interpreter or GC breaks a fixed-rate loop, the CPU or memory budget on the Jetson is exceeded, or the code must run inside a C++ composable node for zero-copy intra-process transport.
- Mention cheaper options first: vectorise with NumPy, use Numba or Cython, or move only the hot kernel into a C++ extension with pybind11 and keep the rest in Python.
- Make the port verifiable: keep the Python version as the reference, generate golden input and output vectors from recorded data, and compare the C++ output within a stated tolerance in CI. pybind11 lets the same Python test harness call the C++ code.
- Name the porting traps: float32 versus float64 differences, NumPy arrays being row-major by default while Eigen matrices are column-major by default, integer overflow that Python never shows, and the order of floating-point accumulation.
Follow-up
- How do you prove the C++ version matches the Python one, and how tight should the tolerance be?
- When would you decide not to rewrite at all?
- How would you structure the code so the algorithm is tested once and used from both languages?
How would you debug a high-latency issue between a publisher and subscriber node in a ROS2 network?
Medium · Robotics & ROS2 Architecture
Approach
- Quantify the latency and where it occurs: compare header.stamp with receive time (ros2 topic delay does this), check ros2 topic hz and bw, and make sure clocks are synchronised (chrony or PTP) before trusting any cross-host number.
- Bisect the path: run both nodes on one host, then in one process with intra-process communication. If the latency disappears, the cause is transport or network; if it stays, it is the subscriber's processing or the executor.
- Check the subscriber side: a single-threaded executor blocked by a long callback, mutually exclusive callback groups serialising work, or a deep history queue so the node processes a growing backlog of stale messages (latency that rises steadily over time points to this). ros2_tracing (LTTng) gives callback-level timing.
- Check QoS and transport: reliable delivery of large messages over a lossy link causes retransmission stalls; large images fragment over UDP and can need bigger socket receive buffers (net.core.rmem_max) or the RMW's shared-memory transport; compressed transport or composition reduces bytes moved.
- Check the platform: CPU contention, thread priorities, and on a Jetson the active power mode (nvpmodel) and clock settings, which change CPU and GPU throughput. Change one variable at a time and re-measure.
Follow-up
- Latency grows steadily the longer the system runs. What does that tell you?
- The problem appears only when the nodes are on different machines. What do you look at next?
- How would you measure latency without synchronised clocks?
Suggested practice checklist
The plan follows the four reported question groups and the three rounds: logistics first, then embedded C++, ROS2 and edge AI work with something to build on each day, then behavioral stories and a full spoken pass.
Day 1 — Map the loop and fix the resume story
- Write a one-page map of the three reported rounds (Initial Alignment, Technical Evaluation, Final Decision Review) and mark which of the four question groups you are least ready for.
- Add your citizenship status or current clearance level to the resume, and write a one-sentence spoken version.
- List three unclassified projects that touched hardware or a robot, and write a short walkthrough of your own contribution to each.
Deliverable: A loop map with the weakest group marked, a resume line on clearance status, and three project walkthroughs.
Day 2 — Memory, ownership and the heap
- Write a C++ RAII class that owns a buffer and follows the rule of five, with the copy operations deleted and move operations implemented; run it under AddressSanitizer.
- Compare a unique_ptr and a shared_ptr in a small program: print sizeof, count allocations, and explain the extra cost of the reference count and control block.
- Write a one-page list of the cases where you would strictly avoid heap allocation on an embedded target and what you would use instead (static storage, fixed pools, preallocated arenas).
Deliverable: A compiled, sanitizer-clean RAII class, a measured shared_ptr versus unique_ptr comparison, and a one-page heap-avoidance list.
Day 3 — Concurrency and cache behavior
- Write a bounded thread-safe queue with a mutex and condition variable, then a single-producer single-consumer ring buffer using std::atomic with acquire and release ordering; run both under ThreadSanitizer.
- Time a loop over an array of structs against a struct of arrays, and a row-major against a column-major traversal of a matrix, then explain the differences with cache lines.
- Say aloud, in under two minutes each, how you would find and fix a race condition and how you would reduce cache misses in a hot loop.
Deliverable: Two queue implementations that pass ThreadSanitizer, a timing table for the two layout experiments, and two spoken answers.
Day 4 — ROS2 communication and QoS
- Run a ROS2 publisher and subscriber and vary reliability, durability and history; record which pairings connect and which silently do not.
- Write a short comparison of ROS and ROS2: central master versus DDS discovery, and what QoS adds for lossy links.
- Write a latency-debugging checklist for a slow publisher and subscriber pair: timestamps at each end, message rate and bandwidth tools, queue depth, executor and callback blocking, serialization size, and network or DDS settings.
Deliverable: A QoS compatibility table from your own runs, a one-page ROS versus ROS2 comparison, and a latency-debugging checklist.
Day 5 — Frames, lifecycle and fail-safe design
- Sketch the TF2 tree for a multi-joint arm (base to each joint to end effector), say which node publishes the transforms and from what source, and note the REP-103 and REP-105 conventions.
- Draw the lifecycle node state machine (unconfigured, inactive, active, finalized) and explain what you would do in each transition for a sensor driver.
- Design a fail-safe that halts a mobile robot if a sensor node stops publishing, using a deadline or liveliness QoS or a watchdog, and write down what the robot does next.
Deliverable: A TF tree diagram, a lifecycle state diagram annotated for a sensor driver, and a one-page fail-safe design.
Day 6 — Edge AI, language trade-offs and Kotlin streams
- Write a Jetson optimization checklist: baseline, ONNX export, TensorRT FP16 and INT8 with calibration, batching, power mode within a 15-watt budget, and the accuracy check after each step.
- Write one scenario in which you prototype in Python and rewrite in C++, with the measurements that would trigger the rewrite, and a short Python versus Kotlin comparison for edge applications.
- Write a Kotlin sample that wraps a callback-based sensor in callbackFlow, conflates it, moves work onto a dispatcher with flowOn, and handles errors with catch.
Deliverable: A Jetson checklist, a prototype-then-rewrite scenario with a language comparison, and a working Kotlin flow sample.
Day 7 — Behavioral stories and a full spoken pass
- Write four STAR stories: debugging with a hardware engineer, your GitLab review and CI workflow, field or client experience, and ambiguous requirements under a deadline; keep each unclassified.
- Rehearse each story aloud twice and cut it to the situation, your actions and a measured result.
- Answer aloud, without notes, the one technical question from days 2 to 6 you found hardest.
Deliverable: Four rehearsed STAR stories on one page each and a recorded spoken answer to your hardest technical question.
Hardware, field and team stories
The reported behavioral prompts for this role cover four situations: debugging a system-level issue with a hardware or mechanical engineer, running source control and code review in a small team, field or client work, and shifting requirements. Candidates also report a tip to use the STAR method and to describe your own technical contribution. Keep every example to open-source, academic or unclassified commercial work.
- Walk through a project where your code read from or controlled physical hardware, and say what you would change about it now.
How do you handle asynchronous data streams in Kotlin using coroutines or flows?
Medium · Edge AI & Python/Kotlin Development
Approach
- Open with the model: a cold Flow runs its producer anew for each collector, while SharedFlow and StateFlow are hot and share one producer. Sensor or telemetry feeds usually enter through callbackFlow, which turns a callback API into a Flow, and awaitClose is where you unregister the callback so nothing leaks.
- Choose backpressure by what the data means. For latest-value telemetry, use conflate() or buffer(1, BufferOverflow.DROP_OLDEST) so a slow collector skips stale samples. For commands or events where every item matters, use a bounded buffer or a Channel and let the producer suspend rather than drop. Never leave an unlimited buffer on a memory-constrained device.
- Put work on the right dispatcher. Use flowOn(Dispatchers.IO) for blocking reads and Dispatchers.Default for CPU work, and keep collection in a scope with a lifecycle (a CoroutineScope with a SupervisorJob that you cancel on shutdown) so structured concurrency cancels the stream with its owner.
- Handle failure inside the pipeline. Use the catch operator for upstream exceptions, retryWhen with a capped backoff for a reconnecting sensor, and a timeout or watchdog that flags a stream silent for too long. Combine streams with combine (latest of each), zip (paired) or merge (interleaved), and say which fits.
- Name the traps. Catching CancellationException and swallowing it breaks cancellation, collecting on the wrong dispatcher blocks the main thread, and a StateFlow drops equal consecutive values. Test with runTest and virtual time.
Follow-up
- What is the difference between StateFlow and SharedFlow, and which would you use for the latest robot pose?
- A collector is slower than the producer. What are your options, and what does each cost you?
- How would you detect and react when a sensor stream goes silent?
Describe a time you had to collaborate with a hardware or mechanical engineer to debug a system-level issue. H
Medium · Behavioral, Clearance & Collaborative Sc
Describe a time you had to collaborate with a hardware or mechanical engineer to debug a system-level issue. How did you isolate the problem?
Approach
- Choose one concrete incident that crossed the software-hardware boundary: for example, intermittent sensor dropouts, motor jitter, or a control loop that missed deadlines. Give the symptom, who else was involved and your own role in two sentences, so the rest of the answer can be about reasoning.
- Walk through isolation as a sequence. Reproduce it reliably, then split the system by layer: record timestamps in the software, ask the hardware engineer to measure the same event with a scope or logic analyzer, swap in a known-good cable, board or build, and change one variable at a time. Say what each step ruled out.
- State the root cause and who confirmed it. A real hardware-software story ends with a cause both of you could verify, such as a power sag, a clock mismatch, an unbuffered signal or a race in your driver, not with whichever side blamed the other first.
- Close with the result and prevention: the fix, how you measured that it held, and what you added afterwards, such as a logging hook, a regression test, a bring-up checklist or a shared timing diagram. Keep the example unclassified and use open-source, academic or commercial work.
- The mistake that sinks it is a story where the hardware person found the bug and you only relayed it, or one with no isolation steps. Name what you did, and what you learned about the other discipline's tools and vocabulary.
Follow-up
- What would you have instrumented sooner if you ran that again?
- What did you do when you and the hardware engineer disagreed about where the fault was?
- How did you confirm the fix addressed the cause and not only the symptom?
How do you manage source control and code reviews using GitLab when working in a fast-paced, small team?
Medium · Behavioral, Clearance & Collaborative Sc
Approach
- Describe a specific workflow, not a feature list: short-lived branches from a protected main, small merge requests, at least one approval, and a required passing pipeline before merge. Say how you keep the unit small enough that a review takes a few minutes rather than days.
- Describe the pipeline stages you would set up: build, unit tests, static analysis or linting (clang-tidy for C++), sanitizer runs, and a stage that runs ROS2 or simulation tests if the project has them. For code that needs real hardware, mention a runner attached to a test rig, or replaying recorded data as a stand-in.
- Show the small-team trade-off. When there are few reviewers, agree a turnaround expectation, use code owners for risky areas such as drivers or safety logic, and mark unfinished merge requests as not ready so reviewers do not spend time early. Use tags for builds that go to field tests, so a deployed version is reproducible.
- Say how you review: read the interfaces and failure paths first, check ownership and thread-safety, ask for a test with every behavior change, and write comments that explain why. Cover a hotfix path during a field test: a branch from the tagged release, a minimal change and a retrospective merge back.
- The weak answer lists GitLab features without a decision in them. Give one real instance where the process caught a defect, or one where it was too slow and you changed it.
Follow-up
- How would you review a large change that touches a driver and a ROS2 node at once?
- How do you test code in CI that needs a physical sensor?
- A teammate keeps merging with failing pipelines to meet a deadline. What do you do?
This role requires approximately 10% to 20% travel for field testing and integration. Describe your experience
Medium · Behavioral, Clearance & Collaborative Sc
This role requires approximately 10% to 20% travel for field testing and integration. Describe your experience working in field environments or directly with clients.
Approach
- Pick the closest honest experience: a field test, a deployment at a customer or lab site, a robotics competition, a hardware bring-up or a demo given to non-engineers. Begin with the setting and what was at stake, then move quickly to what you did there.
- Show preparation and adaptation: the checklist you built, the known-good build you tagged and carried, the backup configuration, and how you worked with poor connectivity and no full development environment, such as recording logs or data for later analysis and making small, reversible changes.
- Include the human side: how you explained a technical problem to a client or operator in plain terms, how you took their feedback, and how you carried what you learned back to the team as a ticket or a test.
- If your field experience is thin, say so plainly and use the nearest analog, then show that you understand the shape of the work: travel, schedule changes and debugging on the spot. The role is described as involving 10% to 20% travel, so a story about enjoying that kind of work lands better than a defensive one.
- Keep the story unclassified. Do not describe prior classified work; use open-source, academic or unclassified commercial examples.
Follow-up
- What went wrong on site, and how did you recover while someone was watching?
- What do you bring to a field test, and what do you check before you leave?
- How do you decide whether to change code on site or log the issue and wait?
How do you handle shifting requirements or ambiguous specifications on a project with tight deadlines?
Medium · Behavioral, Clearance & Collaborative Sc
Approach
- Frame the situation in two sentences: what was unclear or changing, and what the deadline was. Then spend the answer on method. First, write down what you know, what you are assuming and what you cannot yet know, and ask the person who owns the requirement the questions with the highest cost of being wrong.
- Separate decisions that are expensive to reverse from cheap ones. Commit early to the cheap ones and delay the others; isolate the uncertain part behind an interface, such as a sensor abstraction, so a changed requirement touches one module and not the whole system.
- Build a thin end-to-end slice first, so stakeholders react to something running. Share the assumptions in writing and confirm them, and when a requirement shifts, state what it costs in schedule or scope and let the owner choose what to cut.
- Give the outcome with evidence: what shipped, what changed along the way, and how you kept the changes from breaking tested behavior, for example with regression tests that ran on every change.
- The two failure modes are silently guessing and waiting for perfect clarity. Show that you did neither.
Follow-up
- The requirement flipped after you had built most of it. What did you salvage, and what did you throw away?
- Two stakeholders gave you conflicting requirements. How did you resolve it?
- How did you decide what to cut when the deadline would not move?