Interview guides · Armstech Engineers
Armstech Engineers Software Engineer Interview Guide
Armstech Engineers Software Engineer interview guide with reported questions on CAD performance, debugging and maintainability, plus a 7-day plan.
The 60-second brief
Armstech Engineers is an engineering firm whose projects span mechanical, civil and piping infrastructure. Its Software Engineers build software for the firm's engineering divisions. Candidates describe the work as custom automation scripts for CAD software, data pipelines that process structural integrity metrics, and automated systems for large construction projects. Engineers are described as owning a feature from design through deployment while working alongside mechanical, civil and piping engineers who supply the requirements. Your day-to-day customer is therefore another kind of engineer, and the code you write has to produce results they can rely on.
This guide is for candidates interviewing for the Software Engineer position at Armstech Engineers. It covers the five technical and integration questions candidates report, how to answer each one with concrete technique, the stack the role lists (C++, Python or Java, data structures and algorithms, the software development life cycle), the engineering-domain background worth learning, and a seven-day plan. It does not cover pay, and no stage-by-stage order of rounds is reported, so confirm the format with your recruiter.
Candidates describe an initial screening on technical background followed by in-depth coding and design assessments. No round count, order or length is reported, so the guide treats the loop as one preparation focus and has you confirm the format with the recruiter.
Focus areas
- Software Engineering
- CAD Software Engineering
- Role Understanding (Job Requirements)
Overview
The work is software for engineers rather than software for consumers. The reported examples are an automation script that drives CAD software and a pipeline that processes structural integrity metrics, so your code will often run over large files of geometry or measurements and feed results that other engineers act on. That shapes preparation in two ways. First, correctness and performance matter together: you should be able to say how a tool behaves when a drawing set or sensor export is many times larger than the sample it was tested on. Second, you will be expected to explain technical choices in terms a mechanical or civil engineer can follow, which candidates report as part of the role.
The five questions candidates report are open-ended discussion questions rather than puzzles with one correct output. They ask how you would optimize performance on large data sets in CAD applications, how you debug complex logic errors in a distributed system, how you keep code maintainable in long-running engineering projects, how you handle integration between software modules and hardware interfaces, and how programming languages trade off when building tools for civil engineering workflows. Each one rewards a structured answer: a measurement or diagnostic step first, a named technique second, a trade-off third, and an example from your own work to anchor it.
Candidates also report that the stack requirements are broad rather than deep in one tool: one of C++, Python or Java, solid data structures and algorithms, and experience across the software development life cycle, including version control, testing frameworks, code review and continuous integration and deployment. Nice-to-have items are CAD software API development, knowledge of structural engineering principles, and experience with large industrial data sets. Pick the language you will answer in, and be ready to defend it using the language trade-offs question.
Domain knowledge appears in the reported topic breakdown as civil and construction knowledge and mechanical knowledge, alongside CAD software engineering. You are not expected to be a licensed engineer, but learn the vocabulary your answers will touch: units and unit conversion, coordinate systems, assemblies and drawings, tolerances, and what a pipe run or a structural load means as data. Candidates are also advised to review past projects through the lens of performance and scalability, so choose two projects where you can state what you measured and what you changed.
Where to focus your preparation
The only stage reported with any detail is an initial screening followed by coding and design assessments, so this guide lists one preparation focus rather than a sequence of named rounds.
Preparation focus
Candidates report that the process opens with a screening on your technical background and then moves to in-depth assessments covering coding ability and design thinking. They describe the pace as deliberate and say the format can differ by location and team, with a mix of virtual and in-person sessions. No count of rounds, order or length is reported, so ask your recruiter which stages you will face and whether you will write code live. Approach the whole loop the same way: be ready to code in your primary language, to reason from requirements to a design, and to talk through the five reported discussion questions on CAD-scale performance, distributed debugging, maintainability, hardware integration and language trade-offs. Narrate your reasoning as you work, because candidates are advised to explain how they reach a solution and not only to present the final code.
What to demonstrate
- Coding fluency in one of the languages the role lists (C++, Python or Java) and command of data structures and algorithms
- Breaking an ambiguous engineering problem into components, from requirements through architecture to implementation
- Engineering practice around the code: version control, testing, code review and continuous integration
- Explaining technical choices to non-software engineers, including examples of cross-functional collaboration
How to prepare
- Email your recruiter for the stage order, whether you will write code, and whether sessions are virtual or in person
- Answer the five reported questions out loud, each with a measurement or diagnostic step, a named technique and a trade-off
- Write a one-page glossary of the CAD, civil and mechanical terms your answers touch, and practise using them correctly
- Pick two past projects and prepare what you measured and changed for performance and scalability
Where candidates lose points
Answering the CAD-scale performance question with a list of tricks (add caching, use threads) before measuring anything
Start by saying you would profile with a realistic data set to find whether time goes to parsing, computation, memory or I/O. Then name the matching fix, such as a spatial index instead of a full scan, streaming or lazy loading instead of reading the whole file, or a more compact memory layout, and state the complexity before and after.
Reducing distributed-system debugging to "read the logs" with no sequence
Give an ordered process: reproduce or narrow the failing case, correlate events across services with a shared request identifier, state a hypothesis about ordering, duplication or stale state, then bisect by changing one variable. End with a regression test that pins the cause, and be ready to separate the trigger from the underlying fault.
Treating maintainability as slogans such as clean code and good comments
Name mechanisms you have used: module boundaries with stable interfaces, characterization tests before a refactor, code review rules, continuous integration that blocks regressions, and a plan for removing old code paths. Tie each to a long-lived engineering tool, where the code outlasts the people who wrote it.
Answering the language trade-offs question as a personal preference for C++, Python or Java
Compare on criteria that matter for engineering tools: execution speed and memory control, the CAD or analysis libraries and APIs available, safety and error handling, how many engineers outside software must read or script it, and deployment. Then choose a language for one concrete civil engineering tool and justify the choice.
Describing a hardware or cross-discipline integration story without your own role, evidence or result
Say what you personally decided and built, show the trace or test that proved the cause, and give a before-and-after number. Prepare one story where you explained a software constraint to an engineer from another discipline and the decision changed.
Questions & practice
Merge partitioned event streams into one ordered feed with bounded lateness
Hard · k-way merge · watermarks · out-of-order streams
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Identify the heaviest tenants in a five-minute window under memory pressure
Medium · top-k · heavy hitters · streaming
The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.
Approach
- Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
- Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
- State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
- Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.
Follow-up
- The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
- Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?
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
- 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.
- 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.
- 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.
- 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?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
How do you optimize software performance for large-scale data sets in CAD applications?
Medium · Technical and Domain Proficiency
Approach
- Profile before changing anything. Load a representative large model rather than a toy file, and split the cost into parse/load, geometry computation, regeneration and rendering. Measure CPU, memory and I/O separately so the fix targets the stage that actually dominates.
- Replace linear scans and O(n^2) pairwise checks with a spatial index: an R-tree, BVH, octree or uniform grid. A balanced tree answers a range query in roughly O(log n + k). For clash or clearance checks, run a broad phase on bounding boxes and only run exact geometry tests on the candidate pairs it returns.
- Cut the data volume. Reference shared geometry through instances instead of copying it, use level of detail for distant parts, and load or stream only what is in view. Store vertex data in contiguous arrays (structure of arrays) so it is cache-friendly.
- Make edits incremental. Keep a dependency graph with dirty flags so one change recomputes only the affected features, cache tessellations keyed by geometry version, and batch writes into the host API instead of making one call per entity.
- Move heavy work off the UI thread and parallelise independent per-part work. Check whether the host API is safe to call from worker threads; if it is not, compute on copied data and apply the results on the main thread. Edge cases: floating-point tolerances, degenerate geometry, and precision loss at very large coordinates, which you handle by working relative to a local origin.
Follow-up
- How would you find every pair of pipe segments closer than a clearance distance in a model with a million segments?
- What changes if the model no longer fits in memory?
- How would you prove an optimisation did not change the geometric results?
What is your approach to ensuring code maintainability in long-term engineering projects?
Medium · Technical and Domain Proficiency
Approach
- Separate the engineering calculation core (pure functions with explicit inputs, outputs and units) from I/O, UI and the CAD or analysis host API. Put a thin adapter layer around the third-party API so a vendor version change touches one place.
- Test the calculations against hand-checked reference values or published worked examples. Add golden-file regression tests for generated outputs such as drawings, schedules or reports, and run everything in CI on every change.
- Make units and tolerances explicit: use unit-aware types or a strict naming convention, define tolerance constants in one place, and cite the assumption or design-code clause next to the formula that depends on it.
- Back this up with process: code review, linters and static type checks, pinned dependencies, versioned file formats with migration code, and short decision records that explain why a design was chosen. Abstract only once two real cases need it, because premature abstraction is its own maintenance cost.
- Pay debt down incrementally. Wrap legacy code in characterisation tests first, then refactor or replace it piece by piece (the strangler pattern). Track signals such as defect rate, build time and time to onboard a new contributor.
Follow-up
- How do you refactor a legacy calculation module that has no tests?
- The CAD vendor ships an API version with breaking changes. How does your structure limit the damage?
- How do you decide whether a piece of technical debt is worth paying down now?
Can you explain the trade-offs between different programming languages when building tools for civil engineeri
Medium · Technical and Domain Proficiency
Can you explain the trade-offs between different programming languages when building tools for civil engineering workflows?
Approach
- Set the criteria before naming languages: what the host tool exposes (a native C++ SDK, a .NET API or an embedded Python scripting layer), performance needs, available numerical libraries, the team's skills, how the tool is distributed to engineers' machines, and how long it has to be maintained.
- C++: native speed, control over memory layout and direct access to native SDKs. The costs are memory-safety bugs, slower iteration and more complex builds and packaging. It fits geometry kernels and solvers where the inner loops dominate.
- Python: fast to write, with NumPy, SciPy and pandas for numerical and tabular work, which suits scripting and data workflows. Pure-Python loops are slow, and in the default CPython build the GIL stops CPU-bound threads from running in parallel. Vectorise, use multiprocessing or push hot loops into compiled code. Packaging for desktop users takes extra work.
- Java, or C# where the host exposes .NET: garbage-collected memory, strong static typing and mature tooling for large codebases, with costs in runtime startup and native interop. A common hybrid puts a C++ core behind Python bindings (pybind11, for example), which buys speed plus scripting at the price of two toolchains and ABI and build issues.
- Cover the edge cases: floating-point results can differ slightly between languages and libraries, so compare with tolerances. Keep units explicit across language boundaries, and decide which language owns validation so bad input is rejected once.
Follow-up
- Engineers need to process a 2 GB survey point cloud on laptops. Which language do you pick, and why?
- How would you call a C++ library from Python, and what typically goes wrong?
- Your team knows only one of these languages well. How does that change your recommendation?
Describe your process for debugging complex logic errors in a distributed system.
Medium · Technical and Domain Proficiency
Approach
- Pin down the wrong behaviour exactly: which output is wrong, for which requests, on which nodes, and since when. Line that up against deploys, config changes and traffic shifts before forming a theory.
- Rebuild the path of one failing request from a correlation or trace ID that every service propagates, plus structured logs. Order events by causality (parent and child spans, message offsets) rather than wall-clock timestamps, because clocks on different machines drift.
- Test the failure modes specific to distributed systems: a retry with no idempotency key causing duplicate effects, at-least-once or out-of-order message delivery, a check-then-act race between two writers, a stale read from a replica or cache, and a timeout where the operation succeeded but the caller retried anyway.
- Narrow it down by component. Replay the recorded inputs against one service in isolation and compare its outputs, add invariant assertions at service boundaries, and make the suspected interleaving deterministic in a test by injecting delays or pausing a consumer.
- Fix the root cause, not the trigger: for example an idempotency key, a version check or compare-and-set, or ordering by key partition. Then add a regression test that reproduces the interleaving, plus a metric or alert on the invariant so a recurrence shows up immediately.
Follow-up
- The error shows up in roughly one request in ten thousand and never locally. How do you reproduce it?
- Two services' logs disagree about the order of events. Which do you trust, and why?
- How would you confirm in production that your fix worked?
Suggested practice checklist
Each day targets one of the five reported questions or the coding and behavioral skills around them, with a deliverable you can check. Days 2 to 6 end with a spoken answer, and day 7 runs all five together.
Day 1 — Confirm the format and map your material
- Email your recruiter to ask for the stage order, whether you will write code, and whether sessions are virtual or in person
- Choose the one language you will answer in (C++, Python or Java) and list two past projects you can discuss for performance and scalability
- Write a one-page glossary of the CAD, civil, mechanical and piping terms you will need, with units and coordinate systems on it
Deliverable: A sent recruiter email, a named primary language with two chosen projects, and a one-page domain glossary.
Day 2 — Performance on large CAD-scale data
- Generate a large synthetic set of 3D bounding boxes and time a query for all boxes near a point with a full scan, then with a grid or tree index
- Profile a script of your own and write down where the time went: parsing, computation, memory or I/O
- Outline a spoken answer: measure, fix the algorithm, fix memory and I/O, then re-measure
Deliverable: Profiler output and timings before and after the index, plus a written four-step answer outline for the CAD performance question.
Day 3 — Debugging logic errors across services
- Write your debugging sequence for the distributed-system question: reproduce, correlate by request identifier, hypothesize, bisect, pin with a test
- Walk through a duplicate-processing bug scenario aloud, naming the log lines or metrics you would pull
Deliverable: A one-page debugging runbook, a recorded spoken answer, and a solved lease-records practice problem.
Day 4 — Maintainability and refactoring
- Take an old module of yours or an open-source one and write characterization tests that pin its current behaviour
- Refactor one seam behind a clean interface and commit it with the tests passing
- Write your maintainability answer around version control, testing, code review and continuous integration, with one example for each
Deliverable: A before-and-after commit with tests, and a five-point maintainability answer with an example per point.
Day 5 — Language choice and core algorithms
- Build a table comparing C++, Python and Java for a batch analysis tool over structural data and for a CAD plugin, covering speed, libraries, safety, readability and deployment
- Pick one language for each tool and write a two-sentence justification
Deliverable: A completed comparison table with two justified choices, and two solved practice problems.
Day 6 — Hardware integration and behavioral stories
- Write a story for the software-and-hardware integration question covering the boundary you owned, the trace that found the cause, the safeguard you added and a number for the result
- Write a second story about explaining a software constraint to an engineer from another discipline
Deliverable: Two written stories, each with evidence and a number, and spoken run-throughs of both practice prompts.
Day 7 — Full dry run
- Answer all five reported questions aloud in one sitting, in your chosen language where code is involved
- Review your weakest answer and rewrite its measurement step and trade-off
- Write three questions for your interviewers about development practice and how technical debt is handled
Deliverable: A recorded full run, one rewritten answer, and three questions to ask.
Cross-discipline stories to rehearse
Candidates report that the role involves working with mechanical, civil and piping engineers, translating technical ideas for people outside software, and owning features from design through deployment. Prepare stories that show you making a decision at a boundary between software and another discipline, with evidence and a result you can state.
- Tell me about a time you explained a technical constraint to an engineer from another discipline and the decision changed.
- Describe a legacy module you refactored for performance: what you measured first, what you changed and how you protected existing behaviour.
- Walk me through a feature you owned from design to deployment and what went wrong after release.
- Tell me about keeping a deadline while the requirements were still ambiguous.
How do you handle integration challenges between software modules and hardware interfaces?
Medium · Technical and Domain Proficiency
Approach
- Choose a story where you owned the boundary between your software and a physical device or vendor component: a serial, USB or network protocol, a sensor or controller, or a vendor driver. Open with two sentences on what your module needed from the device and what went wrong, such as mismatched byte order or units, timing drift, dropped messages or firmware that differed from its specification. With no hardware experience, use the closest real boundary you owned and say so.
- Spend most of the answer on your diagnostic sequence. Capture raw traffic at the boundary with timestamps (logging bytes, a protocol sniffer or a logic analyzer), compare it field by field against the interface specification, rule out the environment (cabling, power, firmware version, configuration) before touching code, and only then decide which side is at fault. Name the evidence that settled it.
- Show how you made the boundary durable. Put the device behind an adapter interface in your code, build a simulator or replay recorded traces so tests run without the hardware, validate and label units at the edge, add timeouts with bounded retries, and version the message format. Explain how you checked that the simulator matched the real device, since a fake that drifts gives false confidence.
- State the blast radius and the result with numbers: what bad data or stalled test equipment could have followed, and what you measured before and after (failed reads, mismatched values, time lost per test run). Name the disagreement, for example with a firmware engineer over who should change the interface, and how a written contract or trace evidence resolved it. The answer sinks if someone else fixed the fault and you only reported it, or if you cannot say how you knew the cause.
Follow-up
- How did you test the module while the hardware was unavailable, and how did you know the simulator matched the real device?
- How did you decide whether the fault was in your code or in the device's firmware?
- What would you add to the interface contract so the same failure shows up earlier next time?
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
- 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.
- 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.
- 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.
- 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?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?