Interview guides · EOS Defense Systems
EOS Defense Systems Software Engineer Interview Questions & Guide 2026
Debugging practice for EOS Defense Systems software engineer candidates: reproduce it, narrow it, then change one thing per run.
The 60-second brief
As a Software Engineer at EOS Defense Systems, you are at the intersection of high-stakes defense technology and complex systems engineering. This role is critical to the development and maintenance of advanced remote weapon stations and sensor systems. Your work directly influences the reliability, precision, and safety of technology deployed in challenging environments, making your contributions central to the company’s mission.
Treat capacity estimation as a conversion skill rather than a table to memorise: turn a user count and an action rate into requests per second and bytes per day, then name the component that number breaks first. The figure only matters if it changes the design.
Focus areas
- Key idempotency on business facts, not requests
- Enforce audit writes at the data-access boundary
- Model legal holds before modelling record destruction
Overview
You will operate within a high-discipline environment that values precision, rigorous engineering standards, and robust system lifecycles. Unlike consumer software roles, your engineering decisions here carry significant weight, impacting hardware-software integration and critical operational performance. It is a role for those who appreciate structured development environments and want to see their code drive physical, tangible outcomes in the defense sector.
Where to focus your preparation
Treat the sections below as preparation areas and confirm the format with your recruiter.
Preparation focus
No round sequence has been reported for this company, so work the categories below and confirm the format with your recruiter.
What to demonstrate
- Breadth across SQL, experimentation and product reasoning
- Ability to state assumptions before choosing a method
How to prepare
- Drill the practice exercises below and time yourself
- Prepare three quantified stories about decisions you drove
Where candidates lose points
Using soft delete (deleted_at IS NOT NULL) as the answer to a records-retention requirement
It fails both directions at once. The obligation to destroy is unmet because the bytes remain in the primary table, the backups, the search index, the analytics extract and any vendor queue that received a copy, and a competent disclosure request or audit will find them. Meanwhile the record is invisible to staff who legitimately need it, so the organisation loses the ability to answer questions it is still required to answer. Retention needs a destruction pipeline that enumerates derived copies, checks legal hold at execution time under lock, emits a receipt, and fails closed when a copy is unreachable - and the tombstone row that proves destruction happened has to survive the destruction.
Implementing eligibility rules as ordinary application code that ships on the release train, with thresholds as constants and reference tables edited in place
Rules change on statutory effective dates, are frequently backdated, and are sometimes corrected after decisions have already been issued under them. If the only record of a threshold is the value currently deployed, every historical determination becomes unreproducible the moment it changes, and an appeal cannot be adjudicated because nobody can demonstrate what the rule was. The fix is bitemporal: a rule_version keyed on the period it governs, plus the transaction time at which that text was recorded, so you can ask both 'what applied to this case' and 'what did we believe applied when we decided it'. Reference data the rules read has to be versioned the same way, because an UPDATE to a rate table is a silent rewrite of history.
Finishing a solution without stating its complexity
Give time and space in the same breath as the code, and define n explicitly when there are two sizes, since n nodes and m edges are not interchangeable. Space is the half that gets skipped: count the auxiliary structures you allocate and the recursion stack at its deepest, not only the answer you hand back.
Assuming the bug is in the framework
Suspect your own code first: read the stack trace top to bottom, check which versions are actually installed rather than which ones you believe are, and reproduce in isolation before blaming a library that thousands of people run daily. When the fault really is upstream, you need that minimal reproduction to say so credibly anyway.
Questions & practice
Match a rail return file onto payment instructions in one pass
Easy · hashing · reconciliation · idempotency
You hold 400,000 disbursement_instruction rows for a 90-day window (instruction_id, determination_id, benefit_period_start, amount_minor, rail_reference, status) and a return file of 12,000 rows carrying rail_reference, amount_minor, return_code and returned_at. The rail guarantees rail_reference is unique only within 90 days and recycles values afterwards, and it may resend a return it already sent. Produce three buckets: returns matched to exactly one instruction, returns matching none, and returns matching more than one. One pass over each input. State your time and space bounds.
Approach
- Build one hash map from rail_reference to a small list of instruction_ids in a single pass over the instruction rows. Lists, not scalars: recycling means a reference can legitimately collide across the window edge, and you need to observe that rather than let the last writer win. O(n) time, O(n) space for n=400k references.
- Stream the return file and probe. Zero hits goes to unmatchable, one hit to matched, more than one to ambiguous. Do not disambiguate an ambiguous hit by amount_minor: a returned payment and a recycled reference can carry the same amount, and guessing turns a reportable exception into a wrong ledger entry.
- Deduplicate resends on the business fact, not on the file row. A return is uniquely identified by (instruction_id, return_code, returned_at) once matched; a second identical row applies the same compensating state and must be a no-op, which is what makes replaying a redelivered file safe.
- Do not filter the instruction side by rail_reference IS NOT NULL. A NULL reference means the acknowledgement has not been processed, not that nothing was sent, so those rows belong in the map's absence set and drive the separate unacknowledged-but-submitted report.
- Memory: 400k entries of a ~20-byte reference key plus an 8-byte id is roughly 11 MB before hash overhead, comfortably in process. If the window ever widens past what fits, range-partition by the reference prefix and run k passes at k times the read cost rather than reaching for a database join per return row.
Follow-up
- The rail sends a partial return for less than amount_minor. What does your matcher do, and what does the instruction row become?
- A return arrives for an instruction that has already been reversed by a compensating row. How do you tell a duplicate return from a genuine second event?
- You must run this continuously rather than per file. What bounds the map now, and what is the eviction rule?
Replay sixty million determinations after a retroactive rule correction
Hard · bulk-replay · inverted-index · determinism
A regulator corrects three clauses of a ruleset with effect six years back. Sixty million determination rows fall under the affected rule family, each carrying rule_version_id, evidence_snapshot_hash and a trace of the clauses that fired. Evaluation is a pure function costing about 8 ms of CPU, but each replay must rehydrate the frozen evidence snapshot from a content-addressed store capped at 500 fetches per second shared with live traffic. Replaying everything is correct. Explain why it is not viable, and give the algorithm that is.
Approach
- Cost the naive run before optimising it. CPU is not the constraint: 60 million evaluations at 8 ms is about 133 CPU-hours and parallelises freely. The store is: 60 million fetches against a 500 per second ceiling is 120,000 seconds, roughly 33 hours of wall clock while consuming the entire budget live traffic also needs. The run is correct because replay is deterministic and unaffected determinations converge on the existing row through the partial unique index, so it is purely a cost problem.
- Build an inverted index from clause to determination. Either a separate determination_clause table indexed on clause_id, or a GIN index over the trace array. The three corrected clauses then yield a candidate set by index scan instead of a full scan, typically well under one percent of the 60 million rows.
- Recognise that the fired-clause filter alone is unsound. A correction that widens a guard makes a clause newly reachable, so determinations that never reached it before can change now, and those are precisely the denials the correction was issued to reverse. If the trace only records clauses that fired, the filter silently omits the population the regulator cares about.
- Close the gap with what the trace can actually support, and be explicit that the target is a sound superset, not an exact set. If the trace records evaluated-and-false clauses as well as fired ones, include both: every determination that can change is then selected, along with some that cannot — a determination that already passed the widened guard is selected and replays identical, because widening a guard cannot exclude an input it already admitted. That asymmetry is the correct trade. A false positive costs one rehydration; a false negative is an uncorrected determination. If the trace records only fired clauses, fall back to a wider superset: every determination under the affected rule_versions whose effective period intersects the corrected period, which is far larger than the clause filter and still orders of magnitude under 60 million.
- Deduplicate the rehydration, not the evaluation. Distinct evidence_snapshot_hash values are the unit of store work, and a single snapshot attached to several cases in a household or an appeal chain is fetched once and evaluated per determination. Order the candidate scan by hash so the cache hits are adjacent rather than scattered across the run.
- Make the run resumable and rate-limited by construction: a durable cursor over the candidate set, a token bucket sized to the share of the 500 per second ceiling you are permitted, and writes that rely on the uniqueness key rather than a read-then-insert, so a restart after a crash converges instead of producing a second current determination.
Worked solution (40 min)
- Write out the naive budget explicitly: 60,000,000 divided by 500 fetches per second, converted to hours, next to the 133 CPU-hours, and state which number is the constraint.
- Construct a ruleset with a guard clause G and a downstream clause C, where G is a pure reachability guard — it admits or blocks, and changing it changes nothing C reads. Build 1,000 determinations: 60 passed G and fired C, 90 evaluated G and failed it so C was never reached, and 850 took an earlier branch and never evaluated G at all.
- Apply a correction that widens G so all 90 previously-failing determinations now pass it and reach C. Establish ground truth first by replaying all 1,000 and recording which results differ; then run the fired-clause filter and count how many of that changed set it selects.
- Re-run with the filter extended to evaluated-and-false clauses, and compare its candidate set against the control's changed set in both directions separately.
Expected result: The control run changes exactly the 90 that the widened guard now admits. The fired-clause filter selects 60 and none of them changed, so it selects 60 useless candidates and misses all 90 real ones. The extended filter selects 150: a sound superset of the changed set, not a match for it. It contains all 90 changed determinations, and its other 60 candidates are the ones that already fired C — they are selected and replay byte-identical, which is the expected cost of soundness, not a defect. The 850 that never evaluated G are outside both filters and also replay identical, confirming they were correctly excluded rather than luckily unchanged.
Verify it yourself
- Every determination the control run reports as changed appears in the extended filter's candidate set: no false negatives on this fixture. The converse does not hold and must not be asserted — 60 of the filter's 150 candidates replay identical — so the filter is verified as a superset, and the run is sized for 150 rehydrations to correct 90 determinations.
- Re-running the extended filter twice produces the same candidate set and writes no second current determination for any case, which exercises the uniqueness key rather than trusting it.
- Killing the run at a random point and restarting from the cursor produces the same final state as an uninterrupted run.
Follow-up
- The correction changes an amount rather than an outcome. Does your candidate filter change, and does the notice obligation?
- A replayed determination comes back identical. Do you write anything at all, and what does an auditor need to see that you checked it?
- Halfway through the run the regulator issues a correction to the correction. What happens to the work already done?
Find every determination a corrected reference table can change
Medium · graph-traversal · blast-radius · versioning
A reference table (a rate schedule, threshold list or jurisdiction list) is corrected: version v is superseded by v' governing [p1, p2]. You have a graph of about 1,800 rule_version nodes, 40,000 clause nodes and 5,000 reference-table-version nodes, with roughly 90,000 reads edges from clause to table version; each rule_version has a governing date range. Determinations number 60 million, each carrying rule_version_id and effective_from. Return the determination set that could change. Name the traversal, its complexity, and what you do if the graph contains a cycle.
Approach
- Traverse backwards from the corrected table version, not forwards from every rule. Reverse the reads edges once into an adjacency list keyed by table version, then breadth-first from v' to collect the clauses that read it and the rule_versions that own those clauses. One BFS is O(V + E), about 135k nodes and edges; the forward alternative asks reachability per rule_version and is O(V times E).
- Filter each edge by period intersection as you traverse. A rule_version only reads the corrected text if its governing range intersects [p1, p2]; an edge to a rule whose period ended before p1 is a historical fact, not a dependency, and following it inflates the replay set with determinations that provably cannot move.
- Intersect at the determination level with a second predicate, not by scanning 60 million rows: the affected rule_version_id set drives an index range scan on (rule_version_id, effective_from), and effective_from must fall inside [p1, p2]. The graph part is milliseconds; the determination part is what the index has to be built for.
- Detect cycles rather than assuming a DAG. A reference table derived from aggregate determination output and read back by a rule is a real configuration, and it is a defect, but a naive BFS with a visited set will terminate quietly and give an answer that hides it. Mark the cycle, report it, and cap the traversal depth so a malformed edge cannot stall the job.
- Report the set with its provenance: which clause chain connects each rule_version to the corrected table. A blast radius nobody can explain is a number an auditor will not accept, and the same chain is what lets a reviewer overrule an over-broad inclusion.
Follow-up
- The corrected table is read by a clause that is itself behind a feature guard never enabled in that period. Do you keep those rule_versions in the set?
- Two reference tables are corrected in the same maintenance window. Is the combined blast radius the union of the two, and when is it larger?
- The reads edges are inferred from a static scan of clause source rather than declared. What does that do to the soundness of your answer?
Decide what the caseworker queue is allowed to denormalise
Medium · denormalisation · read model · write amplification · identity merge
The queue screen shows 50 cases with constituent display name, program label, assigned unit, days remaining against sla_due_on, and current outcome — data spread over five tables. A separate supervisor view aggregates the same fields across 2M open cases. Product asks to denormalise all five onto case. Decide what you would and would not copy, justify each choice from its read pattern, and describe the write path that keeps any copy correct. Constituent merges rename people across millions of rows, and status is written only by the Case Service.
Approach
- Price the read before redesigning for it. The queue returns 50 rows; resolving five foreign keys per row is 250 primary-key lookups against small tables that live in cache, which is microseconds. Denormalisation buys nothing here, and a candidate who copies columns for this read is solving a problem the numbers do not show.
- Separate a pointer from a copy.
case.current_determination_idis a pointer maintained by the same transaction that setsis_current, so it cannot drift by more than that transaction's atomicity;statusandsla_due_onare case-owned facts, not copies of anything. Neither is denormalisation, and conflating them with copied labels is how the conversation goes wrong. - Refuse the copy that a merge invalidates. Constituent display name must stay behind
constituent_id, because a merge or a corrected legal name would otherwise require rewriting millions of case rows, and any row missed is a person shown under the wrong name on a legal artifact. The cost is not the write volume, it is that the failure mode is silent and personal. - Identify the read that genuinely justifies a copy: the supervisor aggregate over 2M open cases, which cannot use 50-row nested loops. Serve it from a rollup keyed on
(program_code, assigned_unit_id, due_bucket)refreshed from case events, not by wideningcase— a rollup is bounded in size, can be rebuilt from the event log, and a stale rollup shows a stale count rather than a wrong name on a case. - Define the write path and the staleness contract: the Case Service is the sole writer of case status and publishes
case_event; the rollup consumes those events and carries an explicit as-of timestamp that the UI shows. A derived copy whose staleness is invisible is worse than a slow join, because a caseworker will act on it. - State the one column worth copying and why:
determination.outcomeonto the rollup, because the aggregate groups by it and the value is immutable once written — a copy of an immutable fact needs no invalidation path at all, which is the only cheap kind.
Follow-up
- The rollup is 40 seconds behind at intake peak and a supervisor reassigns work from it. What does the reassignment write, and against which row version?
- Full-text search over constituent names is now required across 2M cases. Does that change your answer about copying the name, and what does a merge do to the index?
- How do you rebuild the rollup from scratch after a bug, and what in the event log makes that replay deterministic?
Destroy an expired record without soft-deleting it
Easy · retention · soft delete · legal hold · tombstone
document(document_id BIGINT PK, content_hash BYTEA, storage_uri TEXT, case_id BIGINT NULL, retention_class TEXT, retention_expires_on DATE NULL, legal_hold_id BIGINT NULL, destroyed_at TIMESTAMPTZ NULL, destruction_receipt_id BIGINT NULL) stores one row per attachment; identical bytes are shared across cases through content_hash. retention_expires_on stays NULL until the case closes. Write the query that selects attachments eligible for destruction, and the second predicate that decides whether the stored bytes at storage_uri may also be deleted. Say what the row looks like after destruction and why it is not deleted.
Approach
- Separate the two decisions. Whether an attachment's retention obligation has ended is a property of the row's context:
WHERE retention_expires_on IS NOT NULL AND retention_expires_on <= CURRENT_DATE AND legal_hold_id IS NULL AND destroyed_at IS NULL. Note that a NULLretention_expires_onmeans the triggering event has not happened, so the clock has not started — three-valued logic already excludes it, but say so out loud rather than leaving it to luck. - Whether the bytes may go is a property of the blob, shared across attachments:
NOT EXISTS (SELECT 1 FROM document o WHERE o.content_hash = d.content_hash AND o.document_id <> d.document_id AND o.destroyed_at IS NULL). This needs an index oncontent_hash; without one it is a sequential scan per candidate. Evaluate it after this batch's tombstones are written and inside the same transaction: if two siblings expire together and you test before writing them, each still sees the other as live and the bytes survive a destruction you have already receipted. - Make the hold check happen at execution, not at planning. Run the sweep as two phases: select candidate ids, then in a second transaction
SELECT ... FROM document WHERE document_id = ANY($1) AND legal_hold_id IS NULL AND destroyed_at IS NULL FOR UPDATEand act only on the rows that survive the re-read. A hold placed between the phases must stop the destruction, and the row lock is what makes that check binding. - Describe the post-destruction row precisely: the same
document_id,destroyed_atset,destruction_receipt_idpointing at a receipt row,storage_uriretained as a dangling reference or nulled, andcontent_hashkept so a later disclosure request can be answered with 'these bytes existed and were destroyed on this date under this schedule'. The tombstone is the proof and must outlive the payload. - State what soft delete fails to do: setting
deleted_atleaves the bytes in the primary table, the backups, the OCR text layer, the search index, the analytics extract and the print vendor's queue, so the obligation to destroy is unmet while staff who could legitimately answer a question can no longer see the record. It fails both halves at once. - Fail closed. If a derived copy cannot be reached, the sweep records the attachment as attempted-and-incomplete rather than writing
destroyed_at, because a receipt that claims destruction the system did not achieve is worse than no receipt.
Worked solution (20 min)
- Seed five attachments: (a) past retention, no hold, hash unique to it; (b) past retention but under legal hold; (c)
retention_expires_onNULL; (d) past retention, sharing acontent_hashwith (e); (e) not yet expired, samecontent_hashas (d). - Write the candidate query and confirm it returns exactly two rows — (a) and (d) — then say in one line why (c) is excluded by three-valued logic and (b) by the hold.
- Write the blob-safety
NOT EXISTSpredicate and confirm it is true for (a) and false for (d), because live sibling (e) still points at the same bytes. - Run the sweep end to end and confirm two rows are tombstoned, one blob is deleted and one is retained.
- Re-seed, then implement phase two: re-read the candidates
FOR UPDATEand, from a second session, place a legal hold on (a) between the phases; show (a) is dropped and only (d) is destroyed in that run. - Write the receipt row and the tombstone update in the same transaction, then query the table as an auditor would: prove the destruction happened without being able to read the content.
Expected result: The sweep destroys two of the five attachments. (a) is tombstoned and its bytes are deleted, because no live row shares its content_hash. (d) is tombstoned but its bytes are retained, because live sibling (e) still references the same content_hash. (b) survives — including when the hold is placed after selection and before the FOR UPDATE re-read — and (c) survives because a NULL retention_expires_on means the clock never started. Every destroyed row still exists with destroyed_at and destruction_receipt_id populated.
Verify it yourself
- The candidate query returns exactly two rows, and the blob-safety predicate splits them one-and-one — the two decisions are visibly independent rather than one predicate doing both jobs.
- Re-running the sweep immediately is a no-op:
destroyed_at IS NULLexcludes everything it already handled, so the job is idempotent. - Flipping the hold on and off between the two phases changes the outcome, proving the hold is evaluated at execution rather than at selection.
- The receipt row survives a query that returns no document content, which is the artifact an auditor actually needs.
- After the sweep, live sibling (e)'s
storage_uristill resolves — deleting the shared blob would have orphaned an attachment nobody was allowed to destroy.
Follow-up
- The OCR text layer and the search index both hold extracts of a destroyed document. How does the destruction job know they exist, and what does it do when the search cluster is unreachable?
- A restore from a backup taken before the sweep reinstates destroyed rows. What in your model detects that, and what does it do?
- Two attachments share
content_hashbut carry differentsensitivity_labelvalues. Which one governs what a viewer is allowed to see?
Have you worked on Java threads?
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
How do you conduct developer testing to ensure software quality?
Medium
Approach
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Do you rely on the testers to perform the complete testing of your code?
Medium
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Replay millions of determinations after a retroactive rule fix
Medium · batch replay · idempotency · backpressure · partitioning
A rule version in force for fourteen months is corrected. Every determination decided under it must be re-evaluated against the corrected text and against the evidence snapshot frozen at the time; 3.2 million determinations are in scope. Eligibility performs no I/O and sustains 2,000 to 4,000 evaluations a minute, shared with live intake. A re-run must converge on the existing determination where the outcome is unchanged and supersede it where it is not. Deliverable: work partitioning, the convergence rule, rate control against intake, and the handling of a snapshot that no longer hashes.
Approach
- Compute the floor before choosing a topology. 3.2 million at 4,000 a minute is 800 minutes, about 13 hours, and that assumes the entire service. Capped at 1,000 a minute to leave headroom for intake it is 3,200 minutes, about 53 hours. Make the cap a runtime knob and pick it from the intake p95 you are willing to spend, not from a thread-pool constant.
- Partition into fixed chunks of case_id ranges within the rule version and claim them with SELECT ... FOR UPDATE SKIP LOCKED against a chunk table, so workers scale horizontally and a crashed worker's chunk becomes re-claimable after its lease expires. Do not partition by decision date: daily volume is deadline-shaped, so date chunks are wildly uneven and the tail workers idle while one chunk runs for hours.
- Write the convergence rule explicitly. Evaluate, then compare (outcome, reason_codes, award_amount_minor) against the current determination. Identical means write nothing but record a replay receipt. Different means insert a superseding determination with supersedes_determination_id set and flip is_current in one transaction, relying on the partial unique index UNIQUE (case_id, reopen_cycle) WHERE is_current to reject a concurrent writer instead of checking first under READ COMMITTED, where the check is not atomic.
- Drive worker concurrency from observed eligibility p95 rather than a fixed count. The replay has days of slack; a live submission has a closing window. A replay that pushes p95 past 200 ms is borrowing latency from people who cannot wait.
- Quarantine evidence drift instead of deciding through it. If the canonicalised artifact set no longer hashes to evidence_snapshot_hash, because a document was destroyed under retention or re-scanned, the replay is answering a different question. Route those cases to a manual list rather than re-deciding on today's evidence.
- Check the real bottleneck at the end. Every changed outcome produces a determination, a notice, and possibly a disbursement correction. Against a notice capacity of 1 to 2 million a month, the changed subset, not the evaluation rate, is what sets the wall-clock end date.
Worked solution (35 min)
- Compute the replay duration at 4,000 a minute and again at a capped 1,000 a minute, and state which you would run and what the cap is protecting.
- Write the chunk claim query with SELECT ... FOR UPDATE SKIP LOCKED and describe what happens to a chunk whose worker dies mid-flight.
- Write the convergence branch in full: the comparison tuple, the no-op path with its receipt, and the superseding insert plus is_current flip in one transaction.
- State the quarantine rule for a case whose current evidence set no longer matches evidence_snapshot_hash, and what the case worker sees.
- Estimate downstream notice volume from the changed subset and compare it against a capacity of 1 to 2 million notices a month.
Expected result: 800 minutes at full rate versus roughly 53 hours capped, with the cap justified by intake p95; re-claimable chunks keyed on case_id ranges; a convergence rule that writes nothing when nothing changed and supersedes atomically when it did; a quarantine list for drifted snapshots; and an explicit statement that notice fan-out gates the end date.
Verify it yourself
- Running the replay a second time over the same chunk produces zero additional superseding determinations.
- Two workers racing on one case produce exactly one constraint violation, handled as an ordinary outcome rather than a 500.
- A case whose snapshot hash changed appears on the quarantine list and has no new determination row.
Follow-up
- The replay finds 180,000 changed outcomes and all of them are adverse. What has to happen before any of them takes effect, and what does that do to your schedule?
- Half the workers die at hour nine. What work is lost, what is redone, and what proves nothing was decided twice?
- How do you demonstrate to an auditor that a determination the replay reported as unchanged was genuinely not altered?
Two current determinations survive a partial unique index
Hard · concurrency · partitioning · unique constraint
About twice a week a case has two determination rows with is_current true for one (case_id, reopen_cycle), which UNIQUE (case_id, reopen_cycle) WHERE is_current should make impossible. It has never reproduced in staging, and a build with extra logging ran clean for a week, so the team is calling it a concurrency heisenbug and proposing an advisory lock. The determination table was range-partitioned on decided_at six months ago. Deliver an ordered checklist, state the single query you run first, and explain precisely what its result rules out.
Approach
- Run the discriminating query before adopting anyone's theory: for each offending pair, select both rows' decided_at, decided_by, rule_version_id, supersedes_determination_id and the partition each row physically lives in. The spread in decided_at settles it — milliseconds apart is a genuine race, months apart cannot be one, and no amount of locking would have prevented it.
- Verify the constraint is real and enforcing. List indexes on the parent and on every partition, and check pg_index.indisvalid and indisready: a CREATE INDEX CONCURRENTLY that failed leaves an index present in the catalog, visible in tooling, and enforcing nothing. This costs one query and eliminates a whole class.
- Check the partitioning interaction, which is the likely answer. A unique index on a partitioned table must include every partition key column, so a partial unique index on (case_id, reopen_cycle) alone cannot exist on the parent; it was almost certainly created per partition instead. Per-partition uniqueness permits exactly one current row per partition, so an original determination and a correction re-run months later fall into different partitions and both stand.
- Explain both pieces of the folklore with that mechanism. Staging never reproduced it because seeded data occupies one partition, where the constraint does hold. The logging build proved nothing because the mechanism is not timing-dependent, and a week without an occurrence is entirely consistent with a twice-weekly rate.
- Fix by moving the invariant somewhere it can be global: either partition on something functionally determined by case_id so every row for a case shares one partition, or hold the invariant in a small unpartitioned current_determination table with UNIQUE (case_id, reopen_cycle) and a foreign key to the determination row, written in the same transaction as the supersession.
- Then backfill deliberately: resolve each existing duplicate pair by following supersedes_determination_id, not by ordering on decided_at, because a corrected determination can legitimately carry an earlier effective date than the one it supersedes. Record what was changed, since some of those cases have already been noticed and appealed.
Follow-up
- You switch to hash partitioning on case_id. What does that cost the archival job that currently drops old partitions by date?
- During the backfill, what does the read API return for a case that currently has two current rows?
- Some of these cases were noticed under the wrong current determination. What does correcting them require beyond a database change?
Suggested practice checklist
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Day 1 — Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Day 2 — One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Day 3 — One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Day 4 — Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Day 5 — Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Day 6 — The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Day 7 — Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
Disagreement needs an outcome you did not choose
Conflict answers where you were right and everyone came round are the weakest ones. Stronger: the evidence you went and collected, what would have changed your mind, and what you did in the weeks after the call went against you. Implementing a design you argued against, properly, is a specific and checkable behaviour.
Can you elaborate on your experience with [specific technology listed on your CV]?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Unblock an engineer whose replay creates duplicate determinations
Easy · mentoring · idempotency · replay · constraints
An engineer's bulk replay job, re-running determinations after a rule correction, has left some cases with two rows flagged current and has re-issued notices for cases whose outcome did not change at all. They have been on it for two days and are now proposing a cleanup script to delete the extra rows. Describe unblocking someone in this shape: how you found where their model was wrong, what you had them do first, and how you left them able to handle the same class of bug without you.
Approach
- Get the invariant said out loud before anyone touches code. 'A replay that changes nothing writes nothing' is usually the sentence they have not yet said, and saying it reframes the problem from a concurrency puzzle into a missing no-op check, which is a different and much smaller fix.
- Separate the two failures they have merged into one bug. Duplicate current rows are a concurrency defect the database should have rejected; re-noticing unchanged cases is a logic defect in the job. A cleanup script addresses neither, and running it before the constraint exists guarantees running it again next week.
- Have them reproduce the smaller failure themselves — two concurrent inserts against a table with no partial unique index — rather than describing it. A reproduction they built transfers to the next bug; a diagnosis handed to them does not.
- Be explicit about what you deliberately did not do: you did not take the keyboard, and you did not let the cleanup script go first. Say how you decided that, because with an incident running the wrong answer is to teach instead of to stop the bleeding, and the judgement call is the interesting part.
- Close the loop on repeatability for them: a no-op predicate compared before writing, a dry-run mode whose output is counts by reason code, and agreement on who reviews the next replay before it runs.
Follow-up
- Notices already went to constituents whose determination did not change. What are they told, and who writes it?
- How do you tell whether the duplicate rows came from concurrency or simply from the job being run twice?
- What changes in your approach if an auditor found this rather than the engineer?
Argue against hot-editable eligibility rules in a live console
Hard · bitemporal modelling · reproducibility · design review · rule versioning
A proposal gives policy staff a console that edits eligibility thresholds and clause text in place, effective immediately for all evaluations, on the grounds that waiting for a release delays a statutory change by weeks. In scope: rule_version(rule_version_id, governing period, recorded-at) and determination(rule_version_id, evidence_snapshot_hash, outcome, reason_codes, clause trace). Describe a design you argued against in this shape: what you conceded, the failure you demonstrated rather than described, what you proposed instead, and what was actually decided.
Approach
- Concede the real problem in the first sentence: the release train is genuinely the wrong clock for a statutory effective date, and any argument that terminates in 'wait for the deploy' deserves to lose. The disagreement is about where the versioning lives, not about whether policy staff should be able to act without you.
- Demonstrate the failure instead of describing it. Take a determination decided last quarter, apply the proposed in-place edit to the threshold its clauses read, replay it, and show the outcome or the amount change with no row anywhere recording that anything changed. That is an unappealable decision, and it is the whole argument in one artifact a non-engineer can evaluate.
- Propose the shape that keeps the concession intact: policy staff author a new rule_version with a governing period and a recorded-at time, the same engine evaluates it, and publishing is their action rather than a deploy. The bitemporal pair is what lets you answer both what governed a case and what you believed governed it when you decided, and the second question is the one the correction workflow depends on.
- Extend the same treatment to the reference data the rules read. Rate tables, thresholds and jurisdiction lists are rule inputs, so an UPDATE to one is a silent rewrite of every historical determination that read it; versioning the ruleset while editing its inputs in place leaves the hole exactly where it was.
- Name the residual risk you have created and price it in: publishing a wrong version is now one click by a non-engineer, so the proposal must include a dry run against a sample of live cases reporting outcome deltas by reason code before a version can be made effective. Then say what was decided, including what you instrumented if you lost.
Follow-up
- Policy publishes a version whose governing period starts three months in the past. What happens to determinations already made inside that window, and who chooses?
- What is the rollback for a published rule version that turns out to be wrong, given determinations were issued under it?
- How do you keep the dry run honest when the sample is drawn from cases that are not representative of the enrolment peak?