cswork / libraryOpen practice workspace

Interview guides · Astranis

Astranis Software Engineer Interview Guide

Astranis Software Engineer interview guide: reported rounds, flight C++, state machine and hardware questions, Python test tooling, and a 7-day plan.

The 60-second brief

Astranis builds small, high-throughput geostationary communications satellites (micro-GEO) for commercial and defense customers. Candidates describe software as part of the spacecraft itself: it drives power subsystems, avionics, thermal control and software-defined radios, rather than sitting on top of someone else's hardware. A Software Engineer can land on one of several tracks: C++ flight software, FPGA signal-processing pipelines, cloud-based mission control and telemetry tools, or Python and full-stack tooling for hardware-in-the-loop (HITL) manufacturing and test. Day to day, reports describe working beside avionics, thermal and mechanical engineers and ground operations staff.

This guide is for candidates interviewing for a Software Engineer role at Astranis, whether the target is the embedded and flight track (C++ or C) or the test, production and ground-tools track (Python, with web frameworks such as React and TypeScript in some roles). It covers the three reported stages, all twenty reported questions grouped by subject, and a seven-day plan. It does not cover pay or offer negotiation.

Candidates report three stages over roughly 3-5 weeks: a Recruiter Call, a Technical Screen on a shared coding or whiteboard platform, and a Full Interview Loop made of several focused technical modules.

Focus areas

  • Embedded Systems
  • Python
  • Full-Stack Development

Overview

The reported work splits along a hardware boundary. On the flight side it is C++ (and C) running on avionics and payload processors, where the requirements candidates describe include an RTOS, bare-metal or embedded Linux experience, and bus protocols such as I2C, SPI, CAN, UART and Ethernet. On the test and ground side it is Python for component- and system-level production tools, HITL simulation harnesses, telemetry pipelines and web control panels. Fault detection, isolation and recovery (FDIR) state machines appear on both sides. Decide which track you are interviewing for before you start, because the reported language choice follows the track.

The twenty reported questions fall into four groups of five. Embedded and flight software covers a thread-safe circular buffer, RTOS task priorities, bit masking of telemetry packets, avoiding heap allocation in C++, and debugging memory corruption without a debugger. State machine and systems architecture covers a payload mode machine, sensor-disconnect handling, concurrent ground commands, a ground-station telemetry ingest pipeline and recovery after a processor reset. Hardware-software interfacing and physics reasoning covers solar panel thermal balance, I2C and SPI timing, ADC noise, power-rail failure detection and closed-loop antenna pointing. Automated testing and production infrastructure covers a thermal vacuum chamber script, a HITL harness, fault injection for FDIR, CI on embedded targets and asynchronous Python instrument control.

Reported loops weight Embedded Systems, Python, Full-Stack Development, Automated Testing and Mechanical Fundamentals (first-principles physics). That mix suggests two kinds of practice. First, write real code from a blank editor: a lock-free or mutex-guarded buffer, a bitfield decoder, a table-driven state machine, an asyncio instrument loop. Second, rehearse estimating physical quantities out loud with units and stated assumptions, such as the equilibrium temperature of a sun-facing panel.

Candidates also report that interviewers change a problem's requirements midway, and that state machine sessions add edge cases such as telemetry lost mid-transition or conflicting ground commands after the first version works. Build your solutions so they can absorb that: define safe and error states first, keep transitions in a table, and keep hardware access behind a narrow interface you can mock.

The Astranis interview process

The three stages below come from candidate reports, and the exact order and contents of the later modules can vary by team and track.

Recruiter Call

Candidates report an opening conversation with a technical recruiter, sometimes an engineering manager, that reviews your background, project experience and fit with the role. Treat it as a short technical story rather than a formality: the loop that follows is built around software that touches physical hardware, so your most relevant project should be ready to describe in plain terms.

What to demonstrate

  • How your background and project experience map to the track you are applying for
  • Whether your hardware-adjacent experience is concrete enough to discuss
  • Fit with the role and the company's satellite work

How to prepare

  • Choose one project where your code controlled a device, sensor, robot or test bench, and rehearse a two-minute version covering what broke and how you found it.
  • State clearly whether you are targeting flight C++ or Python test and ground tools, and match your project choice to that track.
  • Check the posting for the citizenship or permanent-resident requirement that candidates report for flight hardware, ITAR-controlled and defense roles, and raise it early if you are unsure.

Technical Screen

Candidates report a technical session of about 45 minutes to an hour, held over video on a shared-code platform such as Coderbyte or a virtual whiteboard, with one or two such screens in some loops. Reports describe live coding, system architecture or first-principles analysis. Because this is the stage where the coding-style questions (the circular buffer, bit masking, memory allocation in C++) are most naturally practised, rehearse them in a plain editor.

What to demonstrate

  • Working code produced live in a shared editor
  • Whether you clarify the problem and state assumptions before typing
  • First-principles reasoning when the numbers or specification are incomplete

How to prepare

  • Write the thread-safe circular buffer and the bitfield packet decoder from a blank file, without autocomplete, and run each against a few hand-made inputs.
  • Practise saying the assumptions aloud first: capacity, producer and consumer count, byte order, what happens when the buffer is full.
  • Do one back-of-envelope physical estimate, such as panel equilibrium temperature, with units written at every step.

Full Interview Loop

Candidates report a series of focused technical modules with engineers from software, avionics and hardware teams, covering software architecture, low-level coding and collaboration. Reports say state machine sessions start from a set of requirements, ask for an implementation or diagram, then add an edge case, and that other follow-ups change requirements part-way through. Expect to move between code, whiteboard diagrams and conversation with people who do not share your specialty.

What to demonstrate

  • Software architecture for flight and ground systems
  • Low-level coding judgement around memory, concurrency and hardware interfaces
  • Collaboration and communication across software, avionics and hardware
  • Adapting a design when requirements change mid-session

How to prepare

  • Build the payload mode state machine in C++ with a table of transitions and named safe and fault states, then add sensor-disconnect handling without rewriting it.
  • Sketch the telemetry ingest pipeline, the HITL harness and the fault-injection framework as component diagrams you can redraw from memory and extend on request.
  • Practise explaining a trade-off (heap versus static pools, polling versus interrupts) in terms an avionics or thermal engineer can check.

Where candidates lose points

Writing transition logic for the payload state machine before naming the safe and fault states

List every mode, the safe state each one falls back to, and what a corrupted or missing input does, then write the transition table. Candidates report that edge cases such as mid-transition telemetry loss arrive after the first version works, so a table-driven design lets you add a row instead of restructuring.

Reaching for std::vector, new, std::function or a blocking mutex in code that runs in a flight path or interrupt context

Use a fixed-capacity buffer, static storage or a pool allocator, and say why: heap fragmentation, unbounded latency and priority inversion. For the circular buffer and the allocation question, name what happens when the buffer is full and which operations may run in an ISR.

Answering the thermal or bus-timing question with a formula or register name but no stated assumptions or units

Write the energy balance in words first (absorbed power in, radiated power out, thermal mass for the transient), list assumptions such as absorptivity, emissivity and eclipse, and carry units through. For I2C and SPI, state clock rate, what the software delay does to the bus, and how a timeout and retry behave.

Treating one ADC sample or one missed packet as truth when designing power-rail or sensor-fault logic

Describe a filter and its delay, a debounce count, hysteresis on thresholds and a plausibility check before any state change. Say how long a failing rail may be tolerated before isolation, and how the isolation is reversed.

Restarting from scratch when the interviewer changes a requirement mid-problem

Keep hardware access behind a small interface and logic in data tables, then say what changes and what stays. Candidates report requirement changes during the loop, so rehearse absorbing a new constraint out loud with a friend instead of silently rewriting.

Questions & practice

Implement a thread-safe cyclic circular buffer in C++ for streaming high-frequency telemetry data from an onbo

Medium · Embedded & Flight Software Engineering

Implement a thread-safe cyclic circular buffer in C++ for streaming high-frequency telemetry data from an onboard sensor.

Approach

  1. Back it with a fixed std::array<T, N> plus head and tail indices, so nothing allocates after construction. With N a power of two the index is count & (N - 1). Use either a separate count or one always-empty slot so that full and empty look different.
  2. For one producer and one consumer, make it lock-free with std::atomic<size_t> head and tail. The producer writes the slot and then stores tail with memory_order_release. The consumer loads tail with memory_order_acquire, reads the slot and stores head with release. Each index then has exactly one writer.
  3. With several producers, or a consumer that needs to block, guard push and pop with a std::mutex and a condition_variable. When the producer is an interrupt handler, no mutex can be used: keep it SPSC or briefly mask the interrupt around the critical section.
  4. Choose a policy for a full buffer and say it out loud. Dropping the newest sample and incrementing an overrun counter keeps the SPSC ownership rule. Overwriting the oldest means the producer has to move head, so it needs a lock or a different design.
  5. Push and pop are O(1) and allocation-free. Put head and tail on separate cache lines with alignas(64) to avoid false sharing, and restrict T to trivially copyable types so copying a slot cannot throw.

Follow-up

  • How does your design change if the producer is a sensor interrupt and the consumer is an RTOS task?
  • How would you show it is race-free: a stress test, ThreadSanitizer, or both?
  • Can you overwrite the oldest sample on overflow without adding a lock?

What mechanisms would you use in modern C++ to prevent dynamic memory allocation overhead during active flight

Medium · Embedded & Flight Software Engineering

What mechanisms would you use in modern C++ to prevent dynamic memory allocation overhead during active flight maneuvers?

Approach

  1. Allocate everything during initialisation and forbid the heap afterwards. One way is to replace global operator new and malloc (for example with -Wl,--wrap=malloc) so they trap or log once an init-complete flag is set, then check the map file for any remaining heap users.
  2. Use fixed-capacity storage: std::array, fixed-capacity containers such as those in the Embedded Template Library, or a std::vector reserved at init (it does not reallocate until size exceeds capacity). Construct objects in static buffers with placement new.
  3. Use pool or fixed-block allocators backed by a free list: O(1) allocate and free, deterministic timing and no fragmentation. Alternatively use std::pmr::monotonic_buffer_resource over a static buffer with std::pmr::null_memory_resource() upstream, so overflow fails loudly instead of falling back to the heap.
  4. Know where allocations hide: std::function with large captures, std::string beyond the small-string buffer, std::shared_ptr control blocks, thrown exception objects, and iostreams. Replace them with templates or a function_ref-style callable, std::string_view, and error codes or -fno-exceptions.
  5. Virtual functions do not allocate; their cost is an indirect call and a vtable pointer. If that matters in a tight loop, CRTP gives static dispatch. Measure before trading away clarity.

Follow-up

  • How would you prove in CI that nothing allocates after initialisation?
  • What should the code do when a pool runs out mid-manoeuvre?
  • Why can heap fragmentation fail a long-running process even when total free memory looks sufficient?

How do you handle concurrency and race conditions when multiple ground commands attempt to update state machin

Medium · State Machine & Systems Architecture

How do you handle concurrency and race conditions when multiple ground commands attempt to update state machine parameters simultaneously?

Approach

  1. Serialise commands. Put every incoming command on one queue consumed by a single task that owns the state machine and its parameters. No other task writes that state, so the data race disappears by design rather than by locking.
  2. Apply multi-parameter updates atomically. Validate the whole set, then publish it with a double buffer and an atomic pointer or index swap, or copy it in under a short mutex. Readers then see either the old set or the new set, never a mix.
  3. Give each command a sequence number so duplicates and replays can be rejected. For conflicting edits, have the command carry the parameter version it was based on and reject it if that version has moved on (optimistic concurrency).
  4. Return an explicit ack or nack with a reason for every command. Let a safe-mode command jump the queue or preempt, and reject parameter changes that are invalid in the current state.
  5. If locks are unavoidable: hold them briefly, never across I/O, take them in one global order to avoid deadlock, and use priority inheritance.

Follow-up

  • Two operators send conflicting parameter changes seconds apart. What does each of them see?
  • A command arrives in the middle of a state transition. Do you queue it, reject it or apply it?
  • How would you test for race conditions here?

Hold a per-tenant active cap against concurrent creates

Hard · write skew · isolation levels · constraints

A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.

Approach

  1. Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
  2. Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
  3. Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
  4. State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.

Follow-up

  • A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
  • The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads 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

  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?

How would you structure a real-time operating system task priority schema to ensure critical flight guidance r

Medium · Embedded & Flight Software Engineering

How would you structure a real-time operating system task priority schema to ensure critical flight guidance runs without task starvation?

Approach

  1. Assign fixed priorities rate-monotonically: the shorter the period, the higher the priority. That puts the guidance and control loop just below the interrupts, then sensor acquisition, then command handling, with telemetry and logging at the bottom.
  2. Show the task set fits. The Liu-Layland utilization bound is n(2^(1/n) - 1), about 69% for large n. A tighter check is response-time analysis: R_i = C_i + sum over higher-priority tasks j of ceil(R_i / T_j) * C_j, iterated until it stops changing, and R_i must not exceed the deadline.
  3. Starvation under fixed priorities only happens when high-priority tasks fail to block. Make periodic tasks sleep with vTaskDelayUntil or an equivalent, bound their worst-case execution time, and keep ISRs short by handing work to a task through a notification or queue.
  4. Prevent priority inversion. Use mutexes with priority inheritance (FreeRTOS mutexes have it; its binary semaphores do not), or a priority ceiling. Better still, keep the guidance task off any lock that low-priority tasks share, and pass data through queues.
  5. Monitor it at runtime: a watchdog that is fed only after every critical task has checked in, deadline-miss and overrun counters, and stack high-water marks such as uxTaskGetStackHighWaterMark.

Follow-up

  • A low-priority telemetry task is blocking a high-priority attitude control thread. How do you diagnose it and fix it?
  • What should the system do when the guidance task overruns its deadline?
  • When would you choose time partitioning or a server-based scheduler over plain fixed priorities?

Explain how bit-wise operations and bit masking are used to decode custom telemetry packets over serial interf

Medium · Embedded & Flight Software Engineering

Explain how bit-wise operations and bit masking are used to decode custom telemetry packets over serial interfaces.

Approach

  1. Start with the frame layout: sync word, length, header, payload and checksum. Parse the byte stream with a small state machine (hunt for sync, read length, accumulate bytes, validate). On a failed check, resynchronise by searching from the byte after the false sync.
  2. Extract a field with (word >> shift) & ((1u << width) - 1). Set flags with |=, clear them with &= ~FLAG, and test them with (status & FLAG) != 0. Use unsigned types throughout, because shifting a set bit into the sign bit of a signed int is undefined.
  3. Build multi-byte fields from individual bytes, e.g. big-endian (uint16_t(b[0]) << 8) | b[1], rather than casting the buffer to a packed struct. A cast breaks on alignment, padding, strict aliasing and implementation-defined bitfield order. Cast uint8_t to uint32_t before shifting by 24, because it is promoted to int.
  4. Sign-extend a w-bit two's-complement field with m = 1u << (w - 1); value = (x ^ m) - m. Then apply the documented scale and offset to get engineering units. Reinterpret float payloads with memcpy or C++20 std::bit_cast.
  5. Check the length against the buffer bounds before reading any field, and verify the CRC (for example CRC-16/CCITT) before acting on the contents. Unit-test the decoder against hand-built byte vectors, including a field that straddles a byte boundary.

Follow-up

  • Write the encoder for the same format and a round-trip test. What does that test catch that decoder tests miss?
  • How do you handle a protocol that sends bits LSB-first within each byte?
  • What should happen to the stream when a frame's length field is corrupted?

Design a state machine in C++ for a satellite payload transitioning between launch, deployment, payload active

Medium · State Machine & Systems Architecture

Design a state machine in C++ for a satellite payload transitioning between launch, deployment, payload active, and safe modes.

Approach

  1. List the states (LAUNCH, DEPLOYMENT, PAYLOAD_ACTIVE, SAFE) and the events (separation detected, deployment complete, deployment timeout, fault detected, ground commands). Write a transition table of (state, event) -> guard, action, next state. Any pair not in the table is rejected and logged, never ignored silently.
  2. Make SAFE reachable from every state on a fault, and leave SAFE only on an explicit ground command whose guard checks power and attitude. Inhibit deployment during LAUNCH until separation is confirmed.
  3. Implement it with an enum class state and either a switch or a const table of handlers, with entry and exit actions. A table is reviewable as data and easy to test exhaustively. The class-per-state pattern scales better for rich behaviour. Neither should allocate at runtime.
  4. Persist the current state and completed one-shot actions (such as each deployment step) to non-volatile memory. After a reset the machine should resume without repeating an irreversible action.
  5. Test every (state, event) pair against the table, including invalid commands in each state, and add timeouts so no state can wait forever for an event.

Follow-up

  • The deployment motor stalls halfway through. Which state are you in, and what happens next?
  • How would you restructure this as a hierarchical state machine if DEPLOYMENT gains substates?
  • A ground command asks for PAYLOAD_ACTIVE while in SAFE. What guards run before you allow it?

Extend a baseline finite state machine to gracefully handle unexpected sensor disconnects or corrupted state t

Medium · State Machine & Systems Architecture

Extend a baseline finite state machine to gracefully handle unexpected sensor disconnects or corrupted state telemetry.

Approach

  1. Validate inputs before they become events. Classify each reading as VALID, STALE or INVALID using range checks, CRC or checksum, a sequence counter or timestamp for staleness, and a rate-of-change limit. The state machine reacts to that classification, not to raw values.
  2. Debounce faults with persistence: declare a sensor failed only after N consecutive bad samples or a timeout, and clear it only after M good ones. That hysteresis stops the machine flapping on a single glitch.
  3. Add degraded behaviour: switch to a redundant sensor, or use the last known good value within a maximum age, or an estimate. Escalate to safe mode only when no trustworthy source remains, and record the reason.
  4. Protect the state variable itself. Store it redundantly (a value plus its bitwise complement, or three copies with a majority vote), range-check the enum on every cycle, and route any default or unknown case to safe mode.
  5. Emit an event-log entry for every fault declaration and recovery, so operators can see why the machine changed state.

Follow-up

  • The sensor flaps between good and bad every few samples. How do you choose N and M?
  • Two redundant sensors disagree and both pass validation. What do you do?
  • How do you test the disconnect handling without unplugging hardware?

Design a ground-station telemetry ingest pipeline capable of parsing and storing thousands of incoming satelli

Medium · State Machine & Systems Architecture

Design a ground-station telemetry ingest pipeline capable of parsing and storing thousands of incoming satellite health parameters per second.

Approach

  1. Size it first. For example, 5,000 parameters per second at roughly 16 bytes each is about 80 KB/s per spacecraft, which is small. The hard parts are ordering, gaps, duplicates and durability, not raw throughput.
  2. Pipeline: the receiver writes raw frames to an append-only archive and a durable log (for example Kafka, partitioned by spacecraft id). Stateless decoders apply a versioned telemetry dictionary. Decoded points go to a time-series store, a limit-checking and alerting stage, and a live dashboard feed over WebSockets.
  3. Handle out-of-order and duplicate delivery by keying each point on (spacecraft, parameter, onboard time or sequence counter) and writing idempotently. Index by spacecraft time, not receive time. Detect gaps from sequence counters so the missing range can be requested for playback.
  4. Batch inserts and let the log absorb bursts. Downsample older data into rollups for long-range queries, and keep raw frames so you can re-decode after a dictionary fix.
  5. Recovery: a crashed decoder resumes from its committed log offset. Version the dictionary alongside the data, so every point can be traced to the definition that decoded it.

Follow-up

  • The dashboard needs data fast but storage must be durable. Where do the two paths split?
  • A new dictionary version changes one parameter's scaling mid-pass. How do you handle it?
  • How would you store years of this data and still answer a one-hour query quickly?

What architectural considerations ensure that a flight software system can recover state automatically followi

Medium · State Machine & Systems Architecture

What architectural considerations ensure that a flight software system can recover state automatically following an unexpected processor reset?

Approach

  1. Read the hardware reset-cause register at boot (power-on, watchdog, brownout, software) and branch on it. Keep a boot counter in retained memory: repeated resets within a window mean a reset loop, so boot into safe mode or a known-good image.
  2. Persist critical state in non-volatile memory with a version, a sequence number and a CRC. Write to alternating A/B slots so a reset mid-write always leaves one valid copy, and on boot load the valid copy with the highest sequence number.
  3. For warm resets, keep a retained-RAM section with a magic value and checksum. Fall back to the non-volatile copy when it fails validation.
  4. Make restart idempotent. Record one-shot actions such as deployments as completed, ordering the write so a reset never repeats an irreversible step. Resume into a conservative state and re-check hardware before returning to active modes.
  5. Balance checkpoint frequency against flash wear. Use wear levelling or FRAM/MRAM for high-rate state, and keep a hardware watchdog so a hang always becomes a recoverable reset.

Follow-up

  • The reset hits during the non-volatile write itself. Walk through what boot finds.
  • The stored state passes its CRC but is wrong, for example because a bit flipped before the write. How do you defend against that?
  • How do you test this recovery path repeatedly?

Walk through how you would model and calculate the thermal balance or temperature of a satellite solar panel u

Medium · Hardware-Software Interfacing & Physics

Walk through how you would model and calculate the thermal balance or temperature of a satellite solar panel using first principles.

Approach

  1. Write the energy balance for a panel at uniform temperature: m c dT/dt = Q_absorbed - Q_electrical - Q_radiated. Q_absorbed is alpha_s * S * A * cos(theta), with S about 1361 W/m^2 near Earth, plus small Earth infrared and albedo terms that you can neglect far from Earth if you say so.
  2. Radiation from both faces: Q_radiated = (eps_front + eps_back) * sigma * A * T^4, with sigma = 5.67e-8 W/m^2K^4 and T in kelvin. Electrical power taken off by the cells is energy that does not become heat, so subtract it.
  3. At equilibrium, set dT/dt = 0 and solve: T = [(alpha_s * S * cos(theta) - P_elec/A) / (sigma * (eps_front + eps_back))]^(1/4). Sanity check: with alpha 0.9, both emissivities 0.85, sun-normal and no power extracted, T comes out around 336 K.
  4. For the transient, integrate with forward Euler using a step much smaller than the thermal time constant. Linearised, that constant is about m c / (4 eps sigma A T^3). Model an eclipse by setting the absorbed term to zero and watching the panel cool.
  5. State your assumptions (uniform temperature, no conduction to the spacecraft body, constant optical properties), carry units through every step, and check the limits: zero input gives steady cooling, and a higher emissivity gives a lower temperature.

Follow-up

  • Add an eclipse and give the minimum temperature at the end of it.
  • How does the answer change when the panel is producing electrical power?
  • How would you validate the model against flight temperature telemetry?

How does an I2C or SPI communication protocol behave, and how do software timing delays impact bus communicati

Medium · Hardware-Software Interfacing & Physics

How does an I2C or SPI communication protocol behave, and how do software timing delays impact bus communication with onboard sensors?

Approach

  1. I2C: two open-drain lines (SDA and SCL) with pull-up resistors, 7-bit addressing, start, stop and repeated-start conditions, and an ACK or NACK on every byte. Standard modes are 100 kHz, 400 kHz and 1 MHz, and a target can hold SCL low to stretch the clock.
  2. SPI: a clock, MOSI, MISO and a chip select per device. It is full duplex, has no acknowledgement and runs much faster. The clock mode (CPOL/CPHA) must match the device, and the chip select has setup and hold times.
  3. Both buses are clocked by the controller, so a software delay usually just stretches a transaction rather than corrupting it. Timing still matters: reading before the sensor's conversion finishes returns stale data, bit-banged buses jitter, and SMBus devices time out if the clock is held low too long (35 ms).
  4. If the controller resets mid-transfer, an I2C target can be left holding SDA low. Recover by toggling SCL up to nine times until SDA releases, then sending a STOP.
  5. Driver design: one owner or mutex per bus, a timeout on every transaction, a bounded number of retries on NACK or timeout, a check of data-ready flags, and counters for errors so a degrading bus shows up in telemetry.

Follow-up

  • The I2C bus is stuck low after a reset. Walk through the recovery.
  • Sensor data is corrupted on the bus whenever the motors draw high current. How do you tell bus noise from a failing sensor?
  • When would you choose SPI over I2C for a new sensor?

Explain how software should account for analog-to-digital converter noise or voltage ripple filter delays when

Medium · Hardware-Software Interfacing & Physics

Explain how software should account for analog-to-digital converter noise or voltage ripple filter delays when parsing power telemetry.

Approach

  1. Characterise the signal before filtering it. One code step is Vref / 2^N. Sample above twice the highest frequency you need (Nyquist) and rely on an analog anti-aliasing filter ahead of the ADC, because no software filter can remove content that has already aliased.
  2. Pick the filter for the noise. Averaging N samples of uncorrelated noise cuts it by sqrt(N). An N-tap moving average adds (N - 1)/2 samples of delay. A first-order IIR, y += alpha * (x - y), has a time constant of about Ts / alpha for small alpha. A median filter rejects isolated spikes.
  3. Account for latency end to end. An RC filter reaches about 63% of a step after one time constant (RC) and about 99% after roughly 4.6 RC, and the software filter adds its own lag. Fault thresholds and persistence windows must allow for both. Fast overcurrent protection belongs in hardware, not behind a filtered software check.
  4. Convert counts to engineering units with calibrated offset and gain and the divider ratio. Sample voltage and current together, or time-align them, before computing P = V * I.
  5. Apply limit checks with hysteresis and persistence, and flag a stuck ADC: a reading that does not change at all over many samples is suspicious when the signal normally carries noise.

Follow-up

  • How do you choose the filter cutoff, given the ripple frequency and the fault you need to detect?
  • Why not just filter harder until the reading is smooth?
  • How would you detect that the ADC or its reference has drifted?

How would you design automated software logic to detect and isolate a failing power rail before it causes a br

Medium · Hardware-Software Interfacing & Physics

How would you design automated software logic to detect and isolate a failing power rail before it causes a brownout?

Approach

  1. Sample voltage and current on every rail at a known rate. Compare them with per-mode limits (a warning band and a fault band, with hysteresis), and also watch the trend: a slow voltage sag or a creeping current shows a failing rail before a hard limit trips.
  2. Confirm before acting: require k of the last n samples out of limits so one glitch does not shed a load. Cross-check against a second measurement when one exists, so a failed sensor is not mistaken for a failed rail.
  3. Split the timing. Protection faster than the software loop belongs in hardware (current-limited load switches, eFuses, comparators). Software handles slower degradation and decides what happens after a hardware trip.
  4. Isolate in steps: switch off the offending load, power-cycle it once in case of a latch-up, shed non-essential loads in priority order to protect the main bus, and fail over to a redundant unit if one exists. Cap retries and lock out after repeated trips so the system cannot oscillate.
  5. Log the measurements and every action to telemetry before and after the response, let ground override the decision, and test each path by injecting faults.

Follow-up

  • How fast does detection have to be, and how would you work that out from the rail's characteristics?
  • What if the failing rail powers the processor running this logic?
  • How do you tell a real overcurrent from a current-sensor fault?

Describe the software-driven closed-loop control logic needed to maintain antenna point alignment during vehic

Medium · Hardware-Software Interfacing & Physics

Describe the software-driven closed-loop control logic needed to maintain antenna point alignment during vehicle momentum dumps.

Approach

  1. Explain the disturbance first. Reaction wheels absorb external torques by spinning up. A momentum dump fires thrusters to apply an external torque while the wheels spin down; magnetic torquers are weak at geostationary altitude. The thruster pulses and the wheel torque transients both disturb pointing.
  2. Close the loop: gyros plus a star tracker feed an estimator (typically a Kalman filter fusing gyro rate with absolute attitude). A controller acting on the attitude-error quaternion commands the wheels. Feed the known dump torque forward so the wheels counter it before an error builds up.
  3. For a PID-style controller, take the derivative term from the measured gyro rate rather than by differentiating the error, add anti-windup so the integrator stops accumulating when the wheels saturate, and run the loop well above the disturbance bandwidth.
  4. Check the result against a pointing budget: the error during the dump must stay inside the antenna beam's allowed error. If it cannot, split the dump into smaller pulses or schedule it when the link can tolerate degradation.
  5. Add guards: wheel speed and torque limits, and a persistence-filtered pointing-error limit that pauses the dump or enters safe mode if it is exceeded.

Follow-up

  • What is integrator windup, and how does it show up during a dump?
  • How would you tune the gains and check stability margins?
  • How does gyro noise or star-tracker dropout affect pointing during the dump?

Write a script to interface with laboratory test equipment to automate a 24-hour thermal vacuum chamber test s

Medium · Automated Testing & Production Infrastru

Write a script to interface with laboratory test equipment to automate a 24-hour thermal vacuum chamber test sequence.

Approach

  1. Wrap each instrument (chamber controller, power supplies, DMMs) in a Python class built on PyVISA with SCPI commands such as *IDN? and *RST. Put timeouts and bounded retries on every query, and write simulated versions of each class so the script runs with no hardware attached.
  2. Describe the sequence as data, for example a YAML list of steps (ramp at a set rate, dwell, run a functional test), and run it with a small state machine. Advance on measured stability, such as the temperature rate of change staying below a threshold for a set period, rather than on fixed sleeps.
  3. Log every sample with a timestamp, flushing each row to CSV or a database so a crash loses nothing. Checkpoint the current step so a restart can resume rather than begin again, and schedule against a monotonic clock.
  4. Check safety limits continuously: temperature, pressure and current drawn by the unit under test. On any violation or unhandled exception, a finally block puts the equipment in a safe state: DUT power off and the chamber held or returned toward ambient.
  5. Send an alert when something fails, and run a full dry run against the simulators before the real test.

Follow-up

  • The script crashes at hour 18. What happens to the equipment and the data, and how do you resume?
  • One instrument stops responding mid-test. What does the script do?
  • How do you test this script without access to the chamber?

How do you construct a Hardware-in-the-Loop (HITL) test harness that mocks hardware inputs and validates fligh

Medium · Automated Testing & Production Infrastru

How do you construct a Hardware-in-the-Loop (HITL) test harness that mocks hardware inputs and validates flight software responses?

Approach

  1. Lay out the rig. The flight computer runs the real flight software. A simulation computer runs dynamics and environment models. Interface hardware turns the simulated sensor values into real bus traffic or analog signals and captures actuator commands to feed back into the model, closing the loop.
  2. Control timing. The simulation must run in real time, time-synchronised with the flight computer, and deterministically, so the same inputs reproduce the same run. Record every input and output for replay.
  3. Write tests as scripts: set initial conditions, inject inputs or commands, then assert on flight telemetry with tolerances and timeouts. Produce pass/fail reports and run the suite automatically.
  4. Layer the fidelity. Run the same test scripts against software-in-the-loop on a host first, then the processor on a bench, then the full hardware rig, using an abstraction layer so the test code does not change between levels.
  5. Build in fault-injection hooks, and version the models, fixtures and flight software together, so every result traces to the exact configuration that produced it.

Follow-up

  • The simulation cannot keep up in real time. What are your options?
  • A HITL test fails one run in twenty. How do you find out whether the rig or the flight software is at fault?
  • There is one rig and many teams. How do you schedule and prioritise test runs?

Design a test framework that injects simulated hardware faults into a state machine to verify fault-detection,

Medium · Automated Testing & Production Infrastru

Design a test framework that injects simulated hardware faults into a state machine to verify fault-detection, isolation, and recovery (FDIR) logic.

Approach

  1. Start with a fault catalogue: sensor stuck, noisy, out of range or disconnected; bus timeouts and corrupted packets; power rail drops; actuator failures; memory bit flips. Parameterise each one by onset time, duration and magnitude.
  2. Inject at a seam: the hardware abstraction layer, behind C++ interfaces or dependency injection, so a fault replaces a driver's response without touching state machine code. Reuse the same fault definitions at bus level on a HITL rig.
  3. Write the assertions for each fault: detected within the required latency, isolated to the correct unit, and recovered to the expected state. Also assert that nominal runs produce no false detections, and check the event log.
  4. Cover a fault-by-state matrix, because the same fault can need a different response in each mode. Add seeded random combinations that can be reproduced, and include transient faults shorter than the persistence filter, which must not trigger a response.
  5. Run it in CI and report which FDIR requirements each test covers, so gaps are visible.

Follow-up

  • How do you test two faults that arrive at the same time?
  • A fault is injected in the middle of a state transition. What should the test assert?
  • How do you decide the coverage is good enough?

Explain how you would structure CI/CD pipelines to run automated unit tests and integration suites on target e

Medium · Automated Testing & Production Infrastru

Explain how you would structure CI/CD pipelines to run automated unit tests and integration suites on target embedded hardware targets.

Approach

  1. Order the stages by cost. First static analysis (clang-tidy, cppcheck, a coding-standard checker), then host builds and unit tests under sanitizers, then a cross-compiled target build (CMake toolchain file), then on-target tests on a hardware farm, then HITL integration suites.
  2. Hardware farm: CI runners attached to boards that can flash over SWD or JTAG, power-cycle through a controllable relay or PDU, and capture UART output. Each job locks a board, has a timeout, and leaves the board recovered for the next job.
  3. Make builds reproducible: pin the toolchain in a container image, stamp the version into the binary, keep artifacts with their hashes, and track binary size and the memory map so growth is caught in review.
  4. Keep pull requests fast: gate merges on host tests plus a short on-target smoke test, and run the full on-target and HITL suites nightly or before release. Quarantine and track flaky tests instead of retrying them blindly.
  5. For releases, sign the artifacts and keep traceability from each requirement to the tests that cover it.

Follow-up

  • There are fewer boards than parallel jobs. How do you schedule them?
  • An on-target test is flaky. How do you work out whether the cause is the board, the rig or the code?
  • How would the pipeline test a firmware update and its rollback?

How do you isolate and debug memory leaks or corruption issues in an embedded environment without a full debug

Medium · Embedded & Flight Software Engineering

How do you isolate and debug memory leaks or corruption issues in an embedded environment without a full debugger attached?

Approach

  1. For leaks, instrument the allocator. Wrap malloc and free with counters, a high-water mark and per-call-site tags, report heap free and minimum-ever-free over UART or telemetry, and watch the trend across many cycles of the same operation: a leak grows steadily.
  2. For stack overflow, paint each task stack with a known pattern at startup and measure how much is untouched. Enable the RTOS overflow hook (FreeRTOS configCHECK_FOR_STACK_OVERFLOW) and record task high-water marks.
  3. For corruption, place canary words around heap blocks and key structures and check them on free and periodically. Use the MPU to fault on writes to guard regions. Make the fault handler save the PC, LR and fault status registers (CFSR on Cortex-M) to retained RAM or flash for post-mortem analysis.
  4. Map the corrupted address to its neighbours with the linker map file. Overruns usually come from the adjacent buffer. If the core lets firmware program a data watchpoint, aim it at the corrupted word to catch the writer.
  5. Reproduce on the host. Build the suspect module for x86 and run it under AddressSanitizer or Valgrind in unit tests. Then bisect commits, or disable subsystems one at a time, until the symptom disappears.

Follow-up

  • The corruption goes away when you add a print statement. What does that tell you?
  • How do you tell a true leak from heap fragmentation?
  • Which of these diagnostics would you keep in flight builds, and why?

Suggested practice checklist

Days 2 to 6 each pick one group of reported questions and end with working code or a written design; day 1 maps the loop and day 7 is a mock with a changed requirement and the behavioral stories.

Day 1 — Map the loop and choose a track

  • Write the three reported stages and note which question types each format naturally covers: coding in the technical screen, architecture, low-level coding and collaboration in the loop.
  • Sort the twenty reported questions into four groups: embedded and flight C++, state machines and architecture, hardware interfacing and physics, test and production infrastructure.
  • Decide whether you are preparing the flight C++ track or the Python test and ground track, and list your two weakest groups.

Deliverable: A one-page loop map, the four question groups with your confidence in each, and your chosen track.

Day 2 — C++ memory, buffers and RTOS priorities

  • Implement a fixed-capacity circular buffer for telemetry samples that is safe for one producer and one consumer, with std::atomic indices and no heap allocation, and add a mutex-based version for multiple producers.
  • List the C++ mechanisms that avoid heap use during flight: static storage, std::array, pool allocators, std::pmr over a static buffer, and what you avoid (exceptions, std::function, virtual calls on the hot path).
  • Write a one-page RTOS priority table for guidance, attitude control, telemetry and housekeeping tasks, covering priority inheritance and deferring ISR work to tasks.

Deliverable: A compiled, tested buffer source file, a written list of allocation-free mechanisms, and a one-page priority table with reasons.

Day 3 — Bit-level decoding and debugging without a debugger

  • Write a decoder for a made-up serial telemetry packet with a header bitfield, a multi-byte field, a checksum and both byte orders, using masks and shifts only, and add unit tests for malformed frames.
  • Write a triage sequence for memory corruption on a target with no debugger: guard bytes and canaries, stack high-water marks, pool usage counters, a memory protection unit if present, and reproducing on a host build with sanitizers.

Deliverable: A decoder with passing tests and a numbered corruption-triage checklist.

Day 4 — State machines and reset recovery

  • Implement the payload mode machine (launch, deployment, payload active, safe) in C++ as a transition table, with the safe and fault states defined before any transition.
  • Extend it to handle a sensor disconnect and corrupted state telemetry, and add a test for each added path.
  • Design how ground commands that change state-machine parameters are serialised: one command queue that owns the state, with validation and a rejected-command response.
  • Write a short note on recovery after a processor reset: what is persisted with a checksum, what the boot path reads, and how the machine re-enters a safe mode.

Deliverable: A compiled state machine with transition tests and a written reset-recovery and command-ordering note.

Day 5 — Physics, buses and sensors

  • Write a function that estimates a sun-facing solar panel's temperature over time from absorbed power, radiated power and thermal mass, and state your assumptions and units.
  • Make a one-page I2C versus SPI sheet: signalling, addressing, clock stretching, acknowledgement, and what software delays or a missing timeout do to a transaction.
  • Sketch software for ADC noise and filter delay, and for a power-rail monitor with debounce and hysteresis that isolates a failing rail.
  • Outline a closed-loop pointing controller (sensor, estimator, PID, actuator limits) that keeps running while momentum is being dumped.

Deliverable: A tested temperature function, a bus cheat sheet, and two short written designs for rail monitoring and pointing control.

Day 6 — Test infrastructure and ground pipeline

  • Write an asyncio script that drives two simulated instruments through a timed sequence, with locks, per-query timeouts, a bounded queue and a safe-state cleanup on failure.
  • Sketch a HITL harness that mocks hardware inputs, plus a fault-injection framework that triggers FDIR logic in the state machine from day 4 and checks the expected recovery.
  • Outline a CI pipeline that runs unit tests on a host build and integration suites on target hardware, and say how a shared board is reserved.
  • Draw the ground-station ingest pipeline for thousands of telemetry parameters per second: decode, validate, buffer, store, and what happens on out-of-order or missing packets.

Deliverable: A runnable instrument-control script with fake instruments and three diagrams (HITL and fault injection, CI, telemetry ingest).

Day 7 — Mock screen with a changing requirement, plus stories

  • Run a timed mock in a plain shared editor on the payload state machine, and have a partner add the sensor-disconnect requirement and a conflicting-command case part-way through while you narrate assumptions aloud.
  • Write stories for hardware-adjacent teamwork: a bug that was not in your code, a requirement change you absorbed, and a disagreement with a hardware owner.

Deliverable: A recorded or written mock run with notes on where you stalled, plus three finished stories.

Hardware-adjacent teamwork and Python instrument control

Candidates report that the Recruiter Call covers your background and fit, and that the Full Interview Loop includes a collaboration module alongside the technical ones, with engineers from software, avionics and hardware teams. For this role that means stories about software meeting physical devices and about working across disciplines. Only the first prompt below is a reported question (and it is technical in substance); the rest are rehearsal prompts built from the reported themes.

  1. Tell me about a time your software controlled a physical device, and a failure turned out not to be in your code. How did you show where it was?
  2. Describe a time a requirement changed while you were mid-implementation. What did you keep, what did you rework, and how did you tell the people depending on you?
  3. Walk me through a disagreement with a hardware, electrical or test engineer about where a fault came from, and how you settled it with evidence.
  4. Why this company and this track, and which project of yours best shows you can work close to hardware?

How do you handle asynchronous data streams in Python when controlling multiple lab instruments simultaneously

Medium · Automated Testing & Production Infrastru

How do you handle asynchronous data streams in Python when controlling multiple lab instruments simultaneously?

Approach

  1. Classify the work first: talking to bench instruments over SCPI on TCP, VISA or serial is I/O-bound, so asyncio fits. Use one task per instrument that owns its connection, and put an asyncio.Lock around every write-then-read pair so two coroutines never interleave commands on the same link.
  2. Most instrument drivers (PyVISA, pyserial) block. Wrap each blocking call in asyncio.to_thread, or run_in_executor on a dedicated pool, so one slow query does not freeze the event loop. If a vendor library is not thread-safe, give it a single-thread executor so calls are serialised.
  3. Move data through bounded asyncio.Queue objects: instrument tasks put timestamped samples (monotonic clock for ordering, wall clock for logs) and one consumer writes to disk. The bound gives backpressure; decide the overflow policy up front (block, drop oldest, or abort the test) and log every drop.
  4. Put a timeout on every query, retry a bounded number of times, and run the tasks inside asyncio.TaskGroup (Python 3.11+) so one failed instrument cancels its siblings. Never swallow CancelledError, and use a finally block to put the chamber and supplies into a safe state.
  5. State the trade-off: threads suit blocking drivers but need locks around shared state, while multiprocessing is for CPU-heavy analysis, not instrument I/O. Test with fake instruments that inject delays, malformed replies and disconnects so the failure paths actually run.

Follow-up

  • One instrument stops replying halfway through a long sequence. What happens to the other instruments and to the chamber?
  • How would you unit-test this without any lab hardware attached?
  • Why asyncio rather than one thread per instrument, and what would make you switch?

Argue against a design, lose, and commit anyway

Medium · disagreement · service boundaries · decision records

Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.

Approach

  1. State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
  2. Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
  3. Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
  4. Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.

Follow-up

  • What threshold on that alert would have proved you right, and did anyone ever look at it?
  • If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?

Estimate work you have never done and defend the range

Hard · estimation · backfills · expand-contract

You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.

Approach

  1. Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
  2. Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
  3. Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
  4. Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.

Follow-up

  • How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
  • Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?