cswork / libraryOpen practice workspace

Interview guides · Grey Matters Defense Solutions

Grey Matters Defense Solutions Software Engineer Interview Questions & Guide 2026

Grey Matters Defense Solutions software engineer prep on partial failure: a timed-out request has an unknown outcome, not a failed one.

The 60-second brief

A Software Engineer at Grey Matters Defense Solutions operates at the critical intersection of advanced algorithms, high-performance computing, and mission-critical defense applications. You are not just writing code; you are building the computational backbone that empowers defense analysts and mission planners to process complex data in high-stakes environments. Whether you are working on CUDA C++ development for real-time acceleration or architecting complex algorithm analysis tools, your work directly influences the speed and accuracy of national security decision-making.

Allocate preparation against your weakest link rather than your favourite topic. A strong algorithm habit usually comes with weak out-loud explanation of tradeoffs, and years of shipping usually come with rusty from-scratch implementation under a clock.

Focus areas

  • Version rules by statutory effective date, not release
  • Key idempotency on business facts, not requests
  • Model legal holds before modelling record destruction

Overview

This role requires a unique blend of technical rigor and mission-focused pragmatism. You will be expected to tackle challenging problems involving large-scale data, signal processing, and system optimization. Because the company's work often supports field-deployed systems, you must balance the desire for innovation with the necessity for reliability and performance. It is an intellectually demanding position that offers the chance to see your technical contributions move from concept to mission deployment.

The Grey Matters Defense Solutions interview process

Grey Matters Defense Solutions candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.

Initial Screening

Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.

What to demonstrate

  • Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
  • Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
  • Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled

How to prepare

  • Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
  • Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
  • Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market

Technical Evaluation

Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.

What to demonstrate

  • Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
  • Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
  • Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
  • Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply

How to prepare

  • For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
  • For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
  • Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last

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.

Computing a statutory due date by adding an interval to a UTC timestamp

The deadline is a civil date in a jurisdiction that observes daylight saving, and a civil day is not always 86,400 seconds; the same arithmetic also ignores weekends and a holiday list that is amended annually and differs by jurisdiction. The result is a due date off by a day near a transition, which reports a case as on time when it is late by law - the direction of the error that produces findings rather than complaints. Store the event as an instant, convert to the case's civil time zone, and walk the business-day calendar as versioned data with its own effective dates, then test the transitions and the holidays explicitly rather than the happy path in July.

Naming no test cases at all

State what you would test before being asked: empty input, a single element, all elements equal, the maximum permitted size, and the input that exercises the branch you just wrote. It costs thirty seconds and is much of what separates someone who has shipped code from someone who has only solved puzzles.

Choosing a schema before the access patterns are known

Write the queries first, with their filters, sort orders, cardinalities and which ones sit on the latency-critical path, then design tables and indexes to serve them. An index nothing queries still costs write throughput and storage, and a hot query with no supporting index becomes a full scan that only hurts once the table is big.

Questions & practice

Parse a fixed-width record with packed-decimal amount fields

Easy · parsing · encoding · legacy-integration

A nightly file from a system of record arrives as fixed-length 180-byte records in EBCDIC code page 037. Per the copybook, bytes 1-40 are a character name field, bytes 41-48 a character date, bytes 49-52 an amount declared PIC S9(5)V99 COMP-3, and the rest character. The trailer record carries a record count and the sum of every amount field. Write the parser and state, in minor currency units, what a whole-record EBCDIC-to-ASCII text conversion does to a packed amount before it ever reaches your validation.

Approach

  1. Parse by field against the copybook offsets, never by transcoding the record. Slice bytes 1-40 and 41-48 and decode those with cp037; slice bytes 49-52 and hand them to a packed-decimal unpacker that never sees a codec.
  2. Unpack COMP-3 explicitly: two BCD digits per byte, and in the final byte the high nibble is the last digit while the low nibble is the sign. Nibble C and F are positive and D is negative under the common convention, so assert the sign nibble rather than defaulting an unrecognised one to positive. S9(5)V99 is seven digits plus a sign nibble, which is eight nibbles, which is exactly the four declared bytes.
  3. Return the value as an integer count of minor units. V99 is an implied decimal position with nothing stored for it, so the seven unpacked digits already are the minor-unit amount; converting to a float to reinsert a decimal point reintroduces the representation error the packed format exists to avoid.
  4. Reject the batch on the trailer, not per record. Accumulate the record count and the integer sum of every amount, compare to the trailer, and fail the whole file rather than post a partial batch. A corrupted amount that still parses is invisible to per-record validation and visible only to the control total.
  5. Complexity is O(total bytes) with O(1) working memory beyond one record buffer and two accumulators; the file is read as a byte stream, so nothing here needs the file resident.

Follow-up

  • The copybook gains an OCCURS DEPENDING ON clause, so record length now varies with a count field. How does your offset arithmetic change?
  • A record contains a sign nibble of 0xA. Is that valid, and what does your parser do with it?
  • The sender starts padding numeric character fields with EBCDIC zeros in a field you decode as text. What breaks and where do you catch it?

Rebuild an award timeline as of a past decision date

Medium · intervals · bitemporal · sweep-line

One case has up to 400 determination rows across all reopen cycles: determination_id, reopen_cycle, supersedes_determination_id, decided_at TIMESTAMPTZ, effective_from DATE, effective_to DATE (NULL meaning open-ended, both endpoints inclusive), award_amount_minor. Given a set of queries (benefit_date D, as_of decision time T), return the award in force for D according to what had been decided by T, plus any benefit dates inside the covered span that no determination covers. Later-decided wins on overlap. Give the complexity for building the answer and for each query.

Approach

  1. Convert every interval to half-open [effective_from, effective_to + 1 day) at the boundary of the system, once. Keeping inclusive ends in the algorithm produces an off-by-one at every adjacency, and the error is a single uncovered or doubly-covered day that a reviewer reads as a gap in benefit.
  2. For a given T, filter to determinations with decided_at <= T. Do not use is_current: that column answers what is in force now, and the whole point of the as-of query is that a determination decided after T must be invisible even though it exists today.
  3. Paint the surviving intervals onto a timeline in ascending decided_at order so later decisions overwrite earlier ones where they overlap. Equivalently, sweep the sorted endpoints with a max-heap keyed on decided_at and lazily discard heap entries whose interval has already ended. O(n log n) build.
  4. Answer each query by binary search over the painted, non-overlapping interval list: O(log n). Gaps fall out of the same structure as the elementary intervals with no covering determination between the minimum effective_from and the maximum effective_to of the surviving set. Note what that definition implies: deleting one determination only produces a gap where nothing else still covers those dates, so an overlapping earlier award absorbs the deletion silently and correctly.
  5. With n at most 400, a quadratic paint over elementary intervals is about 160k comparisons and is genuinely fine; say so rather than reaching for the heap by reflex. The heap earns its place when the same routine runs across a program-wide cohort rather than one case.
  6. If queries carry many distinct T values, the painted timeline is per-T and rebuilding it per query is O(Qn log n). Sort queries by T and advance one sweep through decided_at order, materialising each version as you pass it, or accept the rebuild when Q is small.

Worked solution (30 min)

  1. Build five determinations for benefit year Y: d1, decided in January, pays 50000 minor units for [Jan 1, Dec 31]; d2, decided in June, pays 62000 for [Mar 1, Dec 31]; d3, decided in November on reopen cycle 1, pays 0 for [Jul 1, Sep 30]; d4, decided in December, restores 62000 for [Sep 1, Sep 30]; d5 covers [Jan 1, Jan 31] of year Y+1.
  2. Paint the timeline with T set to the end of July and read the award for a benefit date in August.
  3. Repeat with T set to the end of December and read the same August date, then read a September date under both values of T.
  4. Delete d2 alone, re-run at T in December, and read both the August award and the gap report.
  5. Delete d1 as well, leaving only d3, d4 and d5, and re-run the gap report.

Expected result: At T in July, August returns 62000 and September returns 62000. At T in December, August returns 0 and September returns 62000, because the November decision covers both and the December decision overwrites only September. Deleting d2 produces no gap at all: d1 still pays 50000 for the whole of year Y, so August returns 50000 and the gap report is empty — removing a superseding interval falls back to what it superseded rather than exposing a hole. Deleting d1 as well leaves a surviving span of [Jul 1 of Y, Jan 31 of Y+1] with nothing covering Oct 1 through Dec 31, and the gap report names exactly those dates.

Verify it yourself

  • Every interval in the painted output is half-open, and on the untrimmed fixture each shares an endpoint with its neighbour, so the union has no one-day seam.
  • Any query with T earlier than the first decided_at returns no coverage at all rather than the earliest award.
  • Re-running with the intervals shuffled produces the identical painted list, because the order is imposed by decided_at and not by input order.
  • The gap report and the award query never disagree: on the fixture with d2 deleted both say August is covered, at 50000, and on the fixture with d1 and d2 deleted both say October through December is uncovered.

Follow-up

  • A determination is decided with an occurred-earlier effective_from that reaches back behind an already-settled disbursement. What does the timeline say, and what does that imply for the payment side?
  • Two determinations in different reopen cycles share a decided_at to the microsecond. What is your tiebreak and why must it be total?
  • How do you answer the same question for 200,000 cases in a nightly report without rebuilding 200,000 heaps?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

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?

Model a ruleset that is corrected after decisions were issued

Hard · bitemporal modelling · exclusion constraint · range types · replay

A regulator republishes a rate table with a correction backdated eleven months. Determinations already issued cite rule_version_id. Design rule_version so the system can answer both 'what governed a case submitted on date D' and 'what did we believe governed it when we decided at instant E', and so two rows may govern the same date only when they were recorded at different times. Give the DDL including the constraint that rejects a bad insert, plus the query that lists determinations needing replay.

Approach

  1. Separate the two time axes explicitly and name them: valid time, the statutory period the text governs, and transaction time, the period during which the system believed it. A retroactive correction is precisely a new row whose valid time overlaps an old row's while its transaction time does not, so any model with a single date range cannot represent the situation at all.
  2. Write the DDL with range types so the database can police it: rule_version(rule_version_id INT PRIMARY KEY, program_code TEXT NOT NULL, governs_period daterange NOT NULL, recorded_period tstzrange NOT NULL, text_hash BYTEA NOT NULL, EXCLUDE USING gist (program_code WITH =, governs_period WITH &&, recorded_period WITH &&)), with CREATE EXTENSION btree_gist so the equality operator on a scalar can participate in a GiST exclusion constraint.
  3. Be deliberate about bounds. daterange defaults to [), so an inclusive statutory end date must be written as daterange(d_from, d_to + 1, '[)') or the last governed day falls outside the range — an off-by-one that produces a plausible wrong determination rather than an error. An open-ended current version is an unbounded upper bound, closed by an UPDATE to the transaction-time range when it is superseded.
  4. Write the two queries and show they differ only in one predicate: current belief is governs_period @> $D AND recorded_period @> now(); belief as of the decision is governs_period @> $D AND recorded_period @> $E. Deriving the case's civil date is part of the query, not an afterthought — (c.submitted_at AT TIME ZONE j.time_zone)::date, because casting a timestamptz straight to date silently uses the session's TimeZone and will move a submission across midnight.
  5. State the rule that makes replay honest: neither query is what you replay against. A determination is replayed against the rule_version_id recorded on it, and these queries exist to find determinations whose recorded version is no longer what current belief says should have governed — the work list. Replaying against 'the current rule' answers a different question and destroys the only evidence of what was decided.
  6. Write the work-list query as the join between belief and record, and size it: SELECT d.determination_id FROM determination d JOIN "case" c USING (case_id) JOIN jurisdiction j ON j.code = c.jurisdiction_code JOIN rule_version rv ON rv.program_code = c.program_code AND rv.governs_period @> (c.submitted_at AT TIME ZONE j.time_zone)::date AND rv.recorded_period @> now() WHERE d.is_current AND rv.rule_version_id <> d.rule_version_id. Eleven months of backdating over this data set is a bulk job, so it is chunked on determination_id and each replay lands on the existing determination through the partial unique index rather than inserting a second one.

Worked solution (45 min)

  1. Create the table with btree_gist and the three-column exclusion constraint, then insert an original version and confirm a second row overlapping in both axes is rejected.
  2. Insert the retroactive correction — overlapping governs_period, disjoint recorded_period — and confirm it is accepted, closing the prior row's transaction-time range in the same transaction.
  3. Write both point-in-time queries for a case submitted inside the corrected window and check they return different rule_version_id values.
  4. Build a jurisdiction whose time zone differs from the session's and a submission near local midnight, then compare the civil-date conversion against a naive ::date cast.
  5. Run the work-list query over a seeded set of determinations and confirm it returns exactly those decided under the superseded text.
  6. Replay one of them and confirm it lands on the existing determination chain through the partial unique index rather than creating a parallel current row.

Expected result: The exclusion constraint rejects a genuine double-booking in both axes and accepts a retroactive correction. The two point-in-time queries return different versions for the same case. The work-list query returns exactly the determinations whose rule_version_id differs from current belief, and replaying one produces a superseding row rather than a duplicate current row.

Verify it yourself

  • Inserting two rows overlapping in both governs_period and recorded_period raises an exclusion violation (SQLSTATE 23P01), not a silent accept.
  • For a case submitted on the last governed day, the @> test is true — the [) bound was handled rather than assumed.
  • The naive ::date cast and the jurisdiction-aware conversion disagree for at least one seeded submission, proving the conversion is load-bearing.
  • Re-running the work-list query after the replay returns an empty set, and running it a third time still returns empty — the job is convergent, not oscillating.

Follow-up

  • The correction changes an amount for 200,000 settled determinations. What does the disbursement side owe, and is it one reversal per determination or one per benefit period?
  • A second correction is issued to the first correction's text. What does the exclusion constraint permit, and what does the transaction-time range of the middle version become?
  • Reference data the rules read — a rate table, a threshold list — is stored as ordinary rows. What breaks on replay, and what is the minimum change that fixes it?

Measure time in status from an append-only case event log

Medium · window functions · event log · frame semantics · backdating

case_event(case_event_id, case_id, reopen_cycle, seq BIGINT, event_type TEXT, occurred_at TIMESTAMPTZ, recorded_at TIMESTAMPTZ) is append-only with UNIQUE (case_id, seq). Staff backdate occurred_at; recorded_at is when the row landed. A case can be reopened, so submitted and determined repeat. Write a query returning one row per decision episode with the interval from its submitted to its determined, plus a flag for episodes still open. State which column you order the window by and why the other one gives wrong answers, and say what your query returns for a cycle whose determined carries an occurred_at earlier than its submitted.

Approach

  1. Partition on the real grain, (case_id, reopen_cycle). With case_id alone, an open cycle's submitted takes the next cycle's submitted as its LEAD, so a cycle that never closed is reported closed with an interval spanning a cycle boundary. Keep LEAD(event_type) in the output so that is detectable rather than assumed.
  2. Order by seq, not occurred_at. seq is unique per case by constraint, so the ordering is total and the result is deterministic. occurred_at is asserted by staff: a determined backdated below its own submitted swaps the two rows, the submitted row becomes last in its partition, LEAD returns NULL, and a cycle that was decided is reported still open — it leaves the completed-cycle statistics entirely rather than showing up as a bad number.
  3. Use LEAD over the filtered event set: SELECT case_id, reopen_cycle, occurred_at AS submitted_at, LEAD(occurred_at) OVER w AS determined_at, LEAD(event_type) OVER w AS next_type FROM case_event WHERE event_type IN ('submitted','determined') WINDOW w AS (PARTITION BY case_id, reopen_cycle ORDER BY seq), then keep the rows where event_type = 'submitted'. WHERE is evaluated before window functions, so filtering to two event types first is both correct and cheap.
  4. Separate pairing from measurement, because seq only fixes the first. Ordering by seq guarantees you paired the right two rows; it cannot repair a timestamp a human asserted, so a backdated cycle still yields determined_at - submitted_at below zero. Emit it as computed alongside (determined_at < submitted_at) AS asserted_interval_negative. Do not clamp with GREATEST(..., interval '0') and do not swap the endpoints: the negative value is the only evidence the entry is wrong, and the flag is what keeps it out of the published average without hiding it from the people who can correct it.
  5. Carry the system-time measure beside the asserted one: recorded_at - LEAD(recorded_at) inverted, i.e. LEAD(recorded_at) OVER w - recorded_at. Precondition for calling it non-negative: recorded_at is assigned by the database at insert and appends for one case are serialised — a per-case seq issued under the case row lock gives you both properties at once. If a case can be appended to concurrently, recorded_at can invert against seq too and the flag must cover both measures.
  6. Handle the open episode as data, not as a default: when LEAD returns NULL there is no determined yet, so emit determined_at IS NULL and a still_open flag. Coalescing to now() silently converts a censored observation into a completed one and drags every average down.
  7. Be explicit about frames. LAG and LEAD are not frame-sensitive — they address the partition directly — but the moment you add a running aggregate, the default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, where CURRENT ROW in RANGE mode means the last peer. Ordering on a tie-prone timestamp therefore makes peers of distinct events and sums them all at once; ordering on unique seq makes RANGE and ROWS coincide.

Follow-up

  • The published service standard is in business days in the case's jurisdiction. What does this query have to become, and where does the holiday calendar live?
  • Two determined events exist in one cycle because a correction was recorded. Which one does your query pair, and which one should it pair?
  • Rewrite the still-open flag so a cycle with withdrawn between submitted and determined is excluded rather than counted as open.

How would you drum up business or identify new technical opportunities for the company?

Medium

Approach

  1. Work from the requirement backwards to the design.
  2. Clarify what is being asked and what a complete answer contains.
  3. State your assumptions explicitly before working the problem.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

How do you ensure your code remains maintainable and scalable within a large, complex codebase?

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. Work from the requirement backwards to the design.
  3. State your assumptions explicitly before working the problem.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Size and partition an append-only access log

Medium · partitioning · append-only · write amplification · retention

audit_access_log takes one row per access to a record carrying personal data, from web UI, internal API, batch jobs, report extracts, and support tooling: roughly 35 million rows a day, peaking at 3,000 writes a second, retained six years because audit retention outlives the records it describes. Rows are never updated. Investigators query by subject_id over a date range and by actor_id over a date range; nobody queries by request_id at scale. Choose the partitioning key, the indexes, the write path, and the destruction mechanism, and state what each costs.

Approach

  1. Do the arithmetic first, because it eliminates most designs. At roughly 200 bytes a row including the heap tuple header, 35 million rows a day is about 7 GB a day before indexes, 12.8 billion rows a year, and near 77 billion rows and 15 TB of heap over six years. No single table with a btree on subject_id survives that, so tiering is part of the original design rather than a later migration.
  2. Partition by range on occurred_at, daily or weekly. It is the only column that turns retention into DETACH plus DROP instead of a DELETE that leaves 35 million dead tuples a day for vacuum to chase. Note the constraint this imposes: in Postgres declarative partitioning a unique index on the partitioned table must include every partition key column, so audit_id alone cannot be the global primary key. Use (occurred_at, audit_id), or accept per-partition uniqueness and say so.
  3. Index only the two queries that exist: (subject_type, subject_id, occurred_at) and (actor_id, occurred_at), and keep them on hot partitions. On an append-only table whose physical order correlates with occurred_at, a BRIN index on that column costs kilobytes where a btree costs gigabytes, but it prunes ranges only and is no substitute for the subject lookup, which is a point query.
  4. Specify the write path as a bounded queue, not a fire-and-forget call. Batch 100 to 500 rows per statement, bound the queue, and decide explicitly what happens when it fills: blocking the read is fail-closed and couples read availability to the audit store, while an outbox keeps reads available and needs its own liveness alarm, because a stalled publisher looks exactly like a quiet afternoon.
  5. Age detached partitions into an immutable, content-hashed columnar archive with the same investigator queries available, and keep retained_until on the partition. Audit retention runs on its own schedule and routinely outlives the case it describes, so it cannot inherit the business record's clock.

Worked solution (30 min)

  1. Compute daily and six-year row counts and raw heap bytes from 35 million rows a day, and name the point at which a single unpartitioned table stops being viable.
  2. Write CREATE TABLE ... PARTITION BY RANGE (occurred_at) with the two investigator indexes, and state why the primary key must include occurred_at.
  3. Write the retention step as DETACH PARTITION, archive, then DROP, and compare its cost against DELETE in dead tuples and vacuum work.
  4. Specify the write path: batch size, queue bound, the behaviour when the queue fills, and the alarm that distinguishes a stalled publisher from low traffic.

Expected result: About 12.8 billion rows a year and roughly 77 billion over six years at around 15 TB of heap; daily or weekly range partitions on occurred_at; a composite primary key including the partition key; (subject_type, subject_id, occurred_at) and (actor_id, occurred_at) on hot partitions with BRIN for range pruning; retention by DETACH and DROP; and a stated availability decision on the audit write.

Verify it yourself

  • A subject query over four years touches only the partitions in range, verifiable in the plan.
  • Dropping a partition frees space immediately with no vacuum backlog.
  • A batch job reading 2 million subject records produces 2 million audit rows, and you can state the investigator question that an aggregate row cannot answer.

Follow-up

  • An investigator asks for every access to one constituent across four years. What does that query touch, and what do you do to make it finish?
  • A report extract reads 2 million subject records in one job. How many audit rows, and why would an aggregate row not satisfy the obligation?
  • The outbox publisher stalls for an hour. How do you find out, and what exactly was lost?

Statutory due dates land one day late for evening submissions

Easy · timezone · business calendar · date arithmetic

An audit finds cases the system reported on time that were late by statute. Two clusters: submissions after 19:00 local in a jurisdiction at UTC-05:00, and the week following a spring daylight-saving transition. sla_due_on DATE is computed by taking submitted_at (TIMESTAMPTZ, stored as UTC), calling .date() on it, then walking ten business days against a holiday list hard-coded in the application. Both clusters move the deadline in the same direction. Deliver an ordered checklist, name both defects and the direction of the error, and give the fix with its test matrix.

Approach

  1. Bucket the wrong rows before theorising: convert submitted_at to the jurisdiction's civil time and group by local hour and by jurisdiction_code. If every bad row in cluster one falls between 19:00 and 23:59 local for a UTC-05:00 zone, the start date is the defect, not the walk.
  2. Name defect one precisely: .date() on a UTC instant yields the UTC calendar date. At 19:00 local in UTC-05:00 the UTC date is already tomorrow, so the ten-day walk starts a day late and the due date lands a day late. The error is permissive — the case reads as on time for an extra day, which is the direction that produces audit findings rather than complaints.
  3. Name defect two: any arithmetic done in elapsed seconds. Adding 10 x 86,400 seconds to 2026-03-05T23:30-05:00 gives 2026-03-16T04:30Z, which converts to 2026-03-16T00:30-04:00 after the spring transition — civil date the 16th where ten calendar days is the 15th. A civil day is not always 86,400 seconds, so day arithmetic must happen after conversion to civil time, never before.
  4. Audit the calendar itself: a hard-coded list has no effective dates, no observed-date shift when a holiday falls on a weekend, and no per-jurisdiction opt-out. It also rewrites history when amended, because last year's determinations would be recomputed against this year's list.
  5. Fix: store the instant, resolve the jurisdiction to an IANA zone identifier (a fixed -05:00 offset is wrong half the year), convert to a civil date, then walk business days over a holiday table keyed by (jurisdiction_code, observed_date) with effective_from and effective_to. Recompute the affected rows and report which cases are genuinely late.
  6. Test matrix, not a happy path: both DST transitions, a submission at 23:59 local, a holiday whose observed date shifts off a weekend, a jurisdiction that does not observe it, a leap day, and a case whose jurisdiction changed mid-flight. A test authored and run in July passes every one of these while broken.

Follow-up

  • The recomputation moves 40,000 due dates earlier and some cases are now already past due. What do you do with them, and who is told?
  • Why is persisting the UTC offset instead of the zone identifier wrong, and when is a fixed offset actually correct?
  • The holiday list is amended next year. What stops that amendment changing a due date already computed and noticed?

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.

'We rewrote it in a better framework' is not a story

A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.

How do you handle memory management in high-performance C++ applications?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Name the disagreement and how you resolved it with evidence.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?

Describe a time you had to optimize a complex algorithm to meet strict latency requirements.

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Give the blast radius: what could have broken, and what you measured.
  3. 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?

Explain your experience with parallel computing and how you optimize code for GPU acceleration.

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Close with what you would do differently, concretely.
  3. 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?
  • How did you know your change caused the improvement?

Describe a time you had to troubleshoot a critical system failure under pressure.

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Name the disagreement and how you resolved it with evidence.
  3. Close with what you would do differently, concretely.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?