cswork / libraryOpen practice workspace

Interview guides · Bee Robotics

Bee Robotics Software Engineer Interview Guide

Bee Robotics Software Engineer interview guide: reported rounds, C#/WPF, legacy C and Python questions, behavioral prompts and a 7-day plan.

The 60-second brief

Bee Robotics makes laboratory-based robotic instruments that automate steps such as DNA extraction and PCR master mix preparation for laboratory staff, and candidates describe the products as medical diagnostic devices. A Software Engineer there is reported to work on several layers of that software: touch-screen desktop interfaces written in C# and WPF, mature C-based applications that handle robotic movement and data processing, and Python scripts used to configure robot behavior and automate repetitive development tasks. The reported duties also include reviewing peers' code, documenting fixes, joining risk management reviews, turning feedback from international clients into software requirement specifications, and moving older applications onto newer technology.

This guide is for candidates interviewing for the Software Engineer role at Bee Robotics. It covers the three reported rounds, the thirteen questions candidates report (C# and WPF internals, debugging legacy C, race conditions, refactoring and modularizing, testing, designing an instrument interface, optimizing Python, and four behavioral prompts), and a seven-day plan. The requirements candidates describe ask for C# or Java, Git and GitLab, and design patterns, with Python, C++, embedded C and familiarity with ISO13485 or EN62304 listed as nice to have.

Candidates report three rounds over roughly 3-5 weeks: a Technical Assessment, a Behavioral Interview and Final Round Discussions. The number of rounds is reported to vary with team and seniority.

Focus areas

  • Data Structures & Algorithms (DSA)
  • System Design
  • Graph Algorithms (Graphs)

Overview

The reported work spans two ends of a stack. One end is a desktop application in C# and WPF that laboratory staff operate on a touch screen. The other is older C code that governs robot movement and data handling, plus Python scripts around it. For preparation this means you need two kinds of fluency: explaining how the .NET runtime, data binding and the UI thread behave, and reading unfamiliar C calmly enough to find a fault in it. Candidates are told they do not need to be C experts, but they do need a methodical way of reading and fixing legacy code.

Much of the role is described as change rather than greenfield: migrating older applications, reviewing peers' code, documenting fixes and joining risk management reviews. That shows up in the reported questions about refactoring legacy code without breaking behavior, splitting a monolith into modules, finding the cause of a race condition, and writing unit and integration tests. Have a concrete project from your own history for each of these, with the constraint you faced and the trade-off you chose.

Because the instruments are medical devices, candidates report that quality, documentation and risk management matter alongside feature work, and the requirements list ISO13485 or EN62304 familiarity as a nice-to-have. If you have not worked under such standards, prepare to describe the closest discipline you have used (required reviews, traceable changes, test evidence) and say honestly what you would learn. Reported tips also include explaining why you chose a technology or pattern, admitting gaps and saying how you would close them, and ending with questions about the team's technical challenges and its plans for modernizing the software.

The Bee Robotics interview process

Candidates report these three rounds in this order, over about 3-5 weeks; the count can vary by team and seniority.

Technical Assessment

Candidates report technical coding exercises and architectural discussions in this stage. The evaluation areas they list are object-oriented programming, WPF architecture with data binding and MVVM, and Python for automation and scripting, with thread-safety in UI applications, memory management and inter-process communication as less common topics. Topic breakdowns also list data structures and algorithms, graphs, hash tables and an LRU cache. Approach it as a working session: say your assumptions, code or design in small steps, and explain why you chose each pattern or data structure.

What to demonstrate

  • Working code in the coding exercises
  • Architectural reasoning and trade-offs in the design discussions
  • Command of object-oriented design, WPF data binding and MVVM, and Python scripting, which candidates list as evaluation areas

How to prepare

  • Implement an LRU cache (hash map plus doubly linked list, constant-time get and put) and one iterative graph traversal in C#, and run them against edge cases such as capacity one and a cycle.
  • Outline an MVVM view model with INotifyPropertyChanged and ICommand, and say how background work reaches the UI thread through the Dispatcher.
  • Prepare short spoken answers on managed versus unmanaged code, WPF memory and performance, and the instrument-interface design question.
  • Write a numbered routine for the legacy C debugging question and a one-page staged plan for the monolith-to-modules question.

Behavioral Interview

Candidates report this stage as a conversation about past experiences and problem-solving methodology, built on behavioral questions. The four behavioral prompts reported for this loop concern changing priorities, meeting strict standards under tight deadlines, acting on code review feedback, and balancing innovation with stability in a medical device. Approach it with stories in which you made the decision, in first person, with the check or document that kept quality intact.

What to demonstrate

  • Your account of past work and the decisions you made in it
  • Your problem-solving method as you describe it, step by step
  • How your stories treat testing, review, documentation and risk

How to prepare

  • Write a short outline for each of the four reported prompts, covering situation, your action, the safeguard you kept in place and the result.
  • Choose one story where deadline pressure threatened quality and one where you changed legacy code without changing its behavior.
  • Prepare an honest answer about a technology you have not used and how you would learn it.

Final Round Discussions

Candidates report concluding discussions about overall fit and readiness for the role. Little more is reported about the format, so prepare to revisit both sides of the loop: your own projects with the technical trade-offs you made, and your approach to quality in a regulated setting. This is also the natural place to bring the questions you want answered about the team's technical challenges and its plans for modernizing its software.

What to demonstrate

  • Overall fit and readiness for the role, as the stage is described
  • How you connect your technical background to medical-device software work

How to prepare

  • Review two of your own projects and be ready to explain a trade-off in each, including what you would change.
  • Rehearse a concise answer on how you test and verify changes, covering unit tests, integration tests and the risk of each change.
  • Write three questions about the team's current technical challenges and how its older applications are being modernized.

Where candidates lose points

Reciting definitions for the C# and WPF questions with no mechanism behind them

For managed versus unmanaged code, talk about the runtime: managed code runs under the CLR with garbage collection, unmanaged code owns its memory, and IDisposable or SafeHandle plus P/Invoke are how the two meet. For WPF memory, name concrete leaks and costs: event handlers that keep views alive, bindings to properties without change notification, missing UI virtualization, and work done on the UI thread.

Debugging legacy C by guessing instead of narrowing

Reproduce the fault first, record the build and recent changes, bisect, add targeted logging or use a debugger at the suspect boundary, and say how you would separate a software fault from a hardware or communication fault. Candidates are told a logical approach matters more than C expertise, so narrate each step and what it rules out.

Answering the race condition question with 'add a lock'

Identify the shared state and who writes it, reproduce under stress or with logged thread ids, form a hypothesis about the unsafe window, and only then choose a fix (narrower lock, immutable data, a queue, or marshaling to the WPF Dispatcher). Say how you would prove the fix with a repeatable stress test.

Describing refactoring or modularization as a big rewrite

Describe staged work: pin current behavior with characterization tests, carve out one seam or module at a time, keep the old path running until the new one matches, and keep each step releasable. Give the order of the stages and what you would check at each.

Behavioral stories with no personal decision, or with tests and documentation as afterthoughts

Use 'I' and name the decision you made. In every story say which safeguard stayed in place (review, tests, written record), since quality discipline is a reported theme of this loop's behavioral prompts.

Questions & practice

How do you manage memory and performance when working on a WPF desktop application?

Medium · Technical and Domain Knowledge

Approach

  1. Measure before changing anything: take heap snapshots (Visual Studio diagnostics, dotMemory or PerfView) before and after opening and closing the same view several times; a view or ViewModel count that keeps growing is a leak, and the retention path names the culprit.
  2. Name the usual WPF leaks: event subscriptions from a short-lived view to a long-lived or static publisher (unsubscribe on Unloaded or use WeakEventManager), DispatcherTimers never stopped, and bindings to a plain CLR property on a class without INotifyPropertyChanged, which WPF tracks through PropertyDescriptor and can keep the target alive.
  3. Keep the UI thread free: run I/O and computation with async/await and Task.Run, marshal results back through Dispatcher.InvokeAsync, and coalesce high-frequency device updates (for example to the screen refresh rate) instead of raising PropertyChanged for every sample.
  4. Cut rendering cost: keep UI virtualization on for ListBox, ListView and DataGrid (VirtualizingStackPanel with VirtualizationMode Recycling), and know that hosting them in a StackPanel or setting CanContentScroll to False realizes every item; freeze brushes and geometries; decode images at display size with DecodePixelWidth.
  5. Batch collection changes, since each ObservableCollection Add raises CollectionChanged and a layout pass; replace the collection or use a bulk-update wrapper. An instrument UI that stays open for days turns small leaks into crashes, so add a soak test.

Follow-up

  • How would you prove a leak fix worked rather than just moved the growth somewhere else?
  • A background thread must append rows to a bound ObservableCollection. What are your options, and what does BindingOperations.EnableCollectionSynchronization do?
  • Why does a DataGrid with 50,000 rows freeze the window, and how do you confirm virtualization is active?

What steps do you take to identify the root cause of a race condition in a multi-threaded application?

Medium · Problem-Solving and System Design

Approach

  1. Characterize the symptom: intermittent, worse under load or on more cores, and changed by adding logging or a debugger points to a timing problem. List the shared mutable state on the failing path and, for each access, which lock (if any) guards it.
  2. Make it reproduce more often: a stress harness that runs the operation thousands of times in parallel, and deliberately widened windows (Thread.Sleep, Thread.Yield or random delays at suspect points). Use ThreadSanitizer (-fsanitize=thread) for C and C++, Helgrind for pthreads, and Microsoft Coyote for systematic exploration in .NET.
  3. Log without changing timing: write events with thread id and a high-resolution timestamp into an in-memory ring buffer and dump it on failure; console or file logging takes locks and can hide the race.
  4. Check the common shapes: check-then-act (ContainsKey then Add; use ConcurrentDictionary.GetOrAdd or one lock), non-atomic read-modify-write such as count++ (use Interlocked.Increment), collections enumerated while another thread modifies them, broken lazy initialization, and WPF objects touched off the UI thread, which throws InvalidOperationException.
  5. Fix at the root with the narrowest correct synchronization or by removing sharing (immutable data, message passing to one owning thread), keep a consistent lock order to avoid introducing deadlocks, and prove it with the stress test that failed before and now passes across many runs.

Follow-up

  • Someone adds a Sleep and the bug disappears. Why is that not a fix?
  • What does volatile guarantee in C#, and why is it not enough in C for sharing data between threads?
  • How do you tell a race condition from a deadlock from the symptoms alone?

Archive a resource graph without breaking live references or recursing

Medium · graph traversal · topological order · tenant isolation

Resources reference other resources within a tenant; for the largest tenant the reference table holds up to 2,000,000 nodes and 8,000,000 edges. Archiving a resource must archive everything reachable from it that nothing outside the set still references, refuse when a live external referrer exists, and terminate when references form cycles, which they legitimately do. Produce the archive order and the refusal list, targeting O(V+E). Say what stops the traversal crossing a tenant boundary, and why recursion is the wrong control structure at this size.

Approach

  1. Load the subgraph with the tenant predicate on both endpoints of the edge, not only on the side you started from. Scoping the left table alone is the classic cross-tenant leak: one mis-entered edge then pulls another tenant's resources into the traversal and, worse, into the archive.
  2. Traverse iteratively with an explicit stack. A 2,000,000-node graph can hold a chain deep enough to exhaust a native stack in the low tens of thousands of frames, and that failure is a process crash rather than an error you can return.
  3. Treat cycles as data rather than corruption: compute strongly connected components with Tarjan in O(V+E) using its own explicit stack, then condense. The condensation is a DAG, so a topological order over it gives the archive order, and every member of a component archives in one transaction because no order within a cycle is valid.
  4. Decide refusals with reverse edges. A candidate is archivable only if every in-edge originates inside the candidate set, so build the transpose or count in-degrees restricted to the visited set, and emit each blocked resource with the id of the external referrer, which is the only part of the answer an operator can act on.

Follow-up

  • The graph is read in one query and the archive writes a minute later. What can change in between, and how do you make the write safe?
  • The candidate set is 400,000 resources. Is that one transaction, and if not, what does a half-finished archive look like to a reader?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.

Approach

  1. Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
  2. Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
  3. Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
  4. Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.

Follow-up

  • The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
  • Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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 the difference between managed and unmanaged code in the context of C#.

Medium · Technical and Domain Knowledge

Approach

  1. Define the split precisely: managed code is C# compiled to IL and run by the CLR, which JIT-compiles it and supplies garbage collection, type safety, bounds checks and structured exceptions; unmanaged code is native machine code outside the CLR (C/C++ DLLs, Win32 APIs, COM servers) whose memory the programmer allocates and frees.
  2. Explain how the two meet: P/Invoke via DllImport (or the source-generated LibraryImport in .NET 7+), marshalling of strings, arrays and structs with StructLayout, why blittable types cross without copying, and COM interop through runtime callable wrappers. C++/CLI mixed-mode assemblies are the other route.
  3. Cover lifetime, which is where interop bugs live: the GC never frees native handles or buffers, so wrap them in SafeHandle and IDisposable with using blocks, keep finalizers as a backstop only, and pin buffers (fixed or GCHandle.Alloc with Pinned) while native code holds a pointer, because the GC compacts the heap.
  4. Separate unsafe from unmanaged: C# unsafe blocks with raw pointers still run under the CLR, they only skip verification. Mention the classic crash: a delegate passed to native code as a callback gets collected unless a managed field keeps it alive.
  5. Tie it to the stack candidates describe: wrapping a C library behind a small managed interface keeps marshalling and error-code translation in one place and lets the rest of the C# code be tested without the native DLL.

Follow-up

  • A native library calls back into your C# code and the app crashes hours later with an access violation. What do you suspect first?
  • How would you find a leak of native memory or handles in a process whose managed heap looks stable?
  • When would you choose C++/CLI over P/Invoke for wrapping a C library?

Describe a situation where you had to refactor legacy code without breaking existing functionality.

Medium · Technical and Domain Knowledge

Approach

  1. Tell it as a story with a clear arc: what the code did, why it had to change (a new requirement, a recurring defect, a platform migration), what made it risky (no tests, hidden callers, global state), and what you personally decided.
  2. Show the safety net you built first: characterization (golden-master) tests that record current outputs for representative and edge-case inputs, including behaviour that looks wrong but that callers may depend on.
  3. Describe the technique: find seams (an interface, a function pointer, a wrapper module) where new code can be substituted, then move in small, separately reviewed commits so each step is shippable and easy to revert; for larger swaps, run old and new paths in parallel and compare outputs.
  4. Cover the rollout: feature flag or staged release, what you monitored, and how you would have rolled back; in a regulated setting, also how you documented the change and its impact for review.
  5. Close with a measurable result (defects removed, build time, lines deleted, time to add the next feature) and one lesson; the answer sinks if it is a rewrite story with no evidence that behaviour was preserved.

Follow-up

  • A characterization test showed the existing behaviour was actually a bug. Did you preserve it or fix it, and who did you ask?
  • How did you choose which inputs the characterization tests covered?
  • What would you do differently if the same code had no version history at all?

How do you implement unit and integration tests to ensure compliance with quality standards?

Medium · Technical and Domain Knowledge

Approach

  1. Start from requirements traceability: each software requirement has an id, each test references the ids it verifies, and the test report links back, so a reviewer can show every requirement is verified. EN 62304, the European adoption of IEC 62304, which candidates list as a nice-to-have, scales required verification by software safety class (A, B, C).
  2. Unit level: xUnit, NUnit or MSTest for C#, testing ViewModels without the UI thanks to MVVM; Unity or CppUTest for C modules; pytest for Python scripts. Hide hardware behind interfaces (a motor or pump controller) and substitute fakes, and inject the clock so timeouts are deterministic.
  3. Integration level: run the real application against an instrument simulator that implements the same interface, then hardware-in-the-loop tests for the paths a simulator cannot model (timing, sensor noise); include negative cases such as communication loss, sensor fault and an aborted run.
  4. Make the evidence automatic: run the suites on every merge request in CI (GitLab CI fits the tooling candidates mention), archive results and coverage per build, and treat branch coverage as a signal for untested paths, not a target.
  5. Prioritise by risk: every risk control measure from the risk analysis gets at least one test that fails if the control is removed; keep tests deterministic, because a flaky suite gets ignored.

Follow-up

  • How do you unit test C# code that sends commands to a physical device?
  • An auditor asks you to prove a specific requirement was verified in the shipped build. What do you show?
  • A test fails intermittently on CI. How do you handle it without weakening the suite?

Design a software interface for a robotic instrument that requires high reliability and real-time user feedbac

Medium · Problem-Solving and System Design

Design a software interface for a robotic instrument that requires high reliability and real-time user feedback.

Approach

  1. Clarify scope: which users (lab staff on a touch screen), which operations (load, run, pause, abort, recover), and what real-time means here; Windows is not a real-time OS, so hard timing loops belong in the controller or firmware, and the desktop UI provides soft real-time feedback.
  2. Layer it: WPF views with MVVM, an instrument service that owns the connection and command queue, and a device-communication layer over the controller protocol. Model the instrument as an explicit state machine (Idle, Initializing, Running, Paused, Error, Aborting) so invalid commands are rejected and button enablement follows from state.
  3. Track every command through distinct states (sent, acknowledged, completed, failed, timed out) with a timeout each; push status events from the device (step, position, temperature, progress) and coalesce them to the screen refresh rate so the UI thread never floods.
  4. Design for failure: heartbeat or watchdog to detect lost communication, a safe state decided by the controller rather than the UI, persisted run state so the app can recover after a crash, and an audit log of every command, user action and error with timestamps.
  5. Touch-screen details: large targets, confirmation for destructive actions such as abort, no ambiguous indicators, and error messages that tell the operator what to do next. Put a simulator behind the same interface so the UI and failure paths can be tested without hardware.

Follow-up

  • The connection to the controller drops in the middle of a run. What does the operator see, and what does the instrument do?
  • The operator presses Abort while a move command is still in flight. Walk through the sequence.
  • How would you test the timeout and fault paths without a physical instrument?

How would you approach the transition of a monolithic application to a more modular architecture?

Medium · Problem-Solving and System Design

Approach

  1. Map the current dependencies first: generate a dependency graph of namespaces or source files, find cycles and globals, and propose boundaries along domain lines (device communication, run scheduling, results and reporting, UI) rather than technical layers.
  2. Prefer a modular monolith before separate processes: move each area into its own assembly or library with an interface at the boundary, enforce allowed references in the build, and invert dependencies so the core does not reference the UI or the hardware layer.
  3. Migrate with the strangler pattern: put a facade in front of the old code, route one capability at a time to the new module, keep characterization tests on the facade, and ship each step separately so a regression points at one change.
  4. Isolate legacy C behind a single wrapper so the rest of the system talks to a clean interface; break shared mutable state by passing data explicitly or through events.
  5. State the trade-offs: process or service boundaries add IPC failures, versioning and deployment cost, so only split processes for a concrete reason (fault isolation, independent release); in a regulated product, smaller increments also keep change-impact analysis and re-verification manageable.

Follow-up

  • How do you choose the first module to extract?
  • Two modules you want to separate have a circular dependency. How do you break it?
  • How do you show the modular version behaves identically to the monolith?

Explain how you would optimize a Python script that is currently underperforming in a data-heavy environment.

Medium · Problem-Solving and System Design

Approach

  1. Profile before optimizing: cProfile with pstats or snakeviz to find hot functions, line_profiler inside them, tracemalloc for memory; compare wall time with CPU time to tell whether the script is CPU-bound or waiting on I/O.
  2. Fix the algorithm first: membership tests on a list inside a loop are O(n) each, a set or dict makes them O(1) on average; replace nested-loop matching with a dict keyed on the join field; build strings with join rather than repeated concatenation.
  3. Move per-row Python loops into vectorized NumPy or pandas operations; avoid iterrows and row-wise apply with Python functions; set dtypes and usecols in read_csv, use categoricals for repeated strings, and process large files with chunksize or generators so memory stays flat.
  4. Speed up I/O: columnar binary formats such as Parquet through pyarrow instead of CSV, batched database queries instead of one per row, and threads or asyncio for I/O-bound work, since the GIL is released during blocking I/O. Use multiprocessing or ProcessPoolExecutor for CPU-bound pure Python.
  5. Verify the result: keep a fixed benchmark dataset, confirm the output is identical before and after each change, and report the measured speed-up and peak memory for each step.

Follow-up

  • The script runs out of memory on a file larger than RAM. What do you change?
  • When can multiprocessing make the script slower?
  • Profiling shows most of the time inside a pandas call. How do you dig further?

What is your process for debugging a complex issue in a C-based legacy codebase?

Medium · Technical and Domain Knowledge

Approach

  1. Reproduce first: pin the exact build, configuration and input, make the failure deterministic if possible, and establish what changed since it last worked; with history in Git, git bisect between a known-good and known-bad commit narrows it mechanically.
  2. Map the code around the symptom before editing: navigate with ctags, cscope or an IDE index, list the globals and static state on the path, and work out who owns each buffer and who frees it, since legacy C rarely documents ownership.
  3. Use tools that catch C's failure modes directly: build with -g, -O0 and -Wall -Wextra; run AddressSanitizer and UndefinedBehaviorSanitizer for overflows, use-after-free and signed overflow; Valgrind memcheck when you cannot recompile; gdb hardware watchpoints to catch whoever overwrites a corrupted field; cppcheck or clang-tidy for a static pass.
  4. Check the classic suspects: uninitialized locals (behaviour that changes with optimisation level or an added printf), off-by-one writes into an adjacent struct member, sizeof applied to a pointer instead of an array, strncpy leaving no terminator, integer truncation, and missing volatile on hardware registers or variables shared with an interrupt handler.
  5. Before fixing, write a test that reproduces the bug; keep the fix minimal, record the root cause and the evidence in the ticket and commit message, and look for the same pattern elsewhere in the codebase.

Follow-up

  • The bug vanishes when you add logging or build in debug mode. What does that tell you, and what do you try next?
  • The code only runs on the instrument's embedded target, with no sanitizers available. How do you debug it?
  • How do you decide whether to fix the bug in place or rewrite the surrounding function?

Suggested practice checklist

Day 1 maps the three reported rounds and your gaps, days 2 to 6 work the technical questions in the order the stack layers (C# and WPF, algorithms, legacy C and concurrency, refactoring and Python, instrument design and tests), and day 7 rehearses the behavioral stories and a full run.

Day 1 — Map the loop and the stack

  • Write the three reported rounds in order and one line on what each covers: Technical Assessment, Behavioral Interview, Final Round Discussions.
  • List the stack candidates describe (C# or Java, Git and GitLab, design patterns, Python, C++, embedded C, ISO13485 or EN62304) and rate yourself one to five on each.
  • Pick your three weakest items and attach each to a day in this plan.

Deliverable: A one-page loop map, a self-rating of every stack item, and the three weakest items assigned to days.

Day 2 — C# runtime and WPF internals

  • Write a half-page answer on managed versus unmanaged code covering the CLR, garbage collection, IDisposable and P/Invoke.
  • Sketch an MVVM view model with INotifyPropertyChanged and ICommand, and mark where background work must return to the Dispatcher.
  • List five causes of WPF memory growth or sluggishness and the fix for each.

Deliverable: A written managed-versus-unmanaged answer, an MVVM sketch, and a five-item leak and performance list with fixes.

Day 3 — Coding exercises: hash maps, LRU cache, graphs

  • Implement an LRU cache in C# with a dictionary and a linked list, with constant-time get and put, and test capacity one and repeated keys.
  • Write iterative breadth-first and depth-first traversal and handle cycles and disconnected nodes.

Deliverable: A tested LRU cache, an iterative traversal with its edge-case tests, and written notes on the practice exercise.

Day 4 — Legacy C debugging and race conditions

  • Write your debugging routine for a legacy C codebase as numbered steps: reproduce, narrow, instrument, hypothesize, fix, add a regression test.
  • Write a small multi-threaded program with an unsynchronized counter, expose the race with a stress run and logged thread ids, then fix it and rerun.

Deliverable: A numbered debugging routine, a program showing the race before and after the fix with stress-run output, and your answer to the practice exercise.

Day 5 — Refactoring, modularization and Python performance

  • Outline a refactoring story from your own work: the legacy code, the characterization tests, the steps and how you proved behavior was unchanged.
  • Write a staged plan for moving a monolith to modules, including the first seam you would cut and what stays releasable at each stage.
  • Profile a slow Python script of your own with cProfile, fix the top hotspot, and record the before and after timings.

Deliverable: A refactoring story outline, a staged modularization plan, and a profile showing before and after timings for one real script.

Day 6 — Instrument interface design and testing

  • Sketch the instrument interface: UI layer, command and state layer, device communication layer, how live status reaches the screen, and the safe state on a communication failure.
  • List the failure cases (lost connection, duplicate command, operator cancel mid-run) and how each is surfaced to the user.
  • Write a test plan that separates unit tests from integration tests, using a simulated device to run the integration cases.

Deliverable: A layered interface diagram with a failure-case table, and a test plan splitting unit and integration tests with a simulated device.

Day 7 — Behavioral stories and a full run

  • Write a situation, action, safeguard and result outline for each of the four reported behavioral prompts.
  • Write three questions for the team about its current technical challenges and how its older applications are being modernized.
  • Run the loop aloud: one technical question from each earlier day, then all four stories, and note every place you gave a definition instead of a mechanism.

Deliverable: Four story outlines, three questions for the team, and a list of weak spots found during the spoken run.

Behavioral stories for a regulated-device team

Candidates report a Behavioral Interview built around past experiences and problem-solving methodology. The four reported prompts cover changing priorities, standards under deadlines, acting on code review feedback, and balancing innovation with the stability of a medical device. For this role, each story should name a decision you made and the safeguard (review, test or written record) that stayed in place.

  1. Tell me about a time you traced a defect in code you did not write.
  2. Tell me about a technology you had to learn without prior experience and how you went about it.

Tell me about a time you had to adapt to rapidly changing project priorities.

Medium · Behavioral and Cultural Fit

Approach

  1. Choose a real reprioritization where you made the calls. Open with the situation in two sentences: what was in flight, what changed, and who changed it. Then spend the answer on your first moves rather than on the backstory.
  2. Show triage: list the work in progress, mark what was safe to pause, what was half-done and risky, and what could be dropped, then agree the new order with whoever owned the priority.
  3. Show you protected quality while switching. Say how you left paused work in a known state (green tests, a branch with notes, a feature switch off) so nothing half-finished reached a release, and how you handed it over.
  4. Close with communication and result: which commitments slipped and who was told, what shipped, and what habit you kept afterward. A story that says only 'I stayed flexible and worked harder' shows no decision and sinks the answer.

Follow-up

  • What did you choose not to do, and who agreed to that?
  • How did you decide which in-flight work was safe to pause?
  • What would you do if the new priority conflicted with a verification step you had planned?

How do you ensure your code meets strict industry standards, even under tight deadlines?

Medium · Behavioral and Cultural Fit

Approach

  1. This is a 'how do you' question, so state a repeatable practice and back it with one example. Name what you hold constant when time shrinks (tests for changed behavior, peer review, a traceable change, updated documentation) and what you cut instead (scope, polish, secondary features). Cut scope, not checks.
  2. Give mechanics: small merge requests in GitLab with review required, automated unit tests on each push, a short checklist, and a written note of the risk each change carries.
  3. Tell a story where a deadline pushed against a standard and you took a visible decision: you reduced scope, flagged the risk to the owner, or split the change so the safe part shipped first. Say what the outcome was.
  4. Be honest about regulated work. If you have not worked under ISO13485 or EN62304, describe the closest discipline you have followed and say you would learn the team's procedure rather than claiming experience you lack.
  5. Avoid answers built on working longer hours or on 'I skip tests and add them later'; both suggest quality is the first thing you give up.

Follow-up

  • What do you do if the reviewer is unavailable and the change is due today?
  • Which check would you never skip, and why that one?
  • Tell me about a time you shipped with a known gap. How did you record and close it?

Describe a time you received constructive feedback on a code review and how you acted on it.

Medium · Behavioral and Cultural Fit

Approach

  1. Pick feedback that changed your code in a substantive way, such as a race on shared state, a missing error path, a test gap or an unclear module boundary, not a formatting nit. State the comment in one sentence.
  2. Describe your reaction in steps: you verified the concern (reproduced it or wrote a failing test), asked a clarifying question where the comment was unclear, and answered with evidence where you disagreed instead of defending the code.
  3. Name the fix and the lasting change: a regression test, a checklist item, a lint rule, or a habit such as running your own diff review before opening the request.
  4. Link it to the reviewer side of the job. Candidates describe reviewing peers' code as part of the role, so end with how the experience changed the way you write review comments.
  5. Avoid a story where the reviewer was simply wrong, or where the feedback was trivial; both read as not having taken feedback seriously.

Follow-up

  • Tell me about a review comment you disagreed with and how it was resolved.
  • What do you look at first when reviewing a change to legacy C code?
  • How do you word feedback to a colleague so it is acted on?

How do you balance the need for innovation with the stability requirements of a medical device?

Medium · Behavioral and Cultural Fit

Approach

  1. State a position first: innovation in this setting means small, reversible, verified changes, and stability is a constraint you design around. Migrating older applications is part of the reported role, so show you can do it without destabilizing a working instrument.
  2. Name concrete mechanisms: characterization tests that pin down current behavior before you touch legacy code, incremental steps instead of a rewrite, a feature switch or parallel run, a regression suite, a risk assessment for each change, and a rollback path.
  3. Tell one story where you improved something (a migration, refactor or tooling change) and proved behavior stayed the same, for example by replaying recorded inputs and comparing outputs. Give what you measured and the result.
  4. Acknowledge the medical-device context. Where risk review and documentation are required, you follow the team's process instead of working around it. If your experience is in another safety-sensitive area, say so plainly and use it as the example.
  5. Avoid both extremes: 'stability always wins' shows no initiative, and 'move fast' with no safeguards is a poor fit for an instrument that handles diagnostic samples.

Follow-up

  • Which change would you decline to make on a released instrument, and why?
  • How would you demonstrate that a migrated module behaves identically to the original?
  • How do you persuade a cautious colleague to adopt a new tool or pattern?