As a Software Engineer at Kratos Defense & Security Solutions, you work on mission-critical technology. You will be contributing to complex, high-stakes systems that support defense, satellite communications, and aerospace initiatives. Your work directly affects the reliability and performance of systems that Kratos customers depend on for national security and global operations.
This role is not merely about writing code; it is about engineering robust solutions in environments where precision and security are paramount. You will collaborate with multidisciplinary teams—including systems engineers, hardware specialists, and project managers—to navigate the full lifecycle of product development. Whether you are working on network programming, real-time control systems, or specialized software interfaces, your contributions will be central to the technological edge that Kratos Defense & Security Solutions provides.
Recruiter Screen
reportedThe title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.
What to demonstrate
- Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
- Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
- Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year
How to prepare
- Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
- Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
- Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter
Hiring Manager Discussion
reportedPart of what is being decided is what your manager's week looks like once you are on the team: how they find out that something you shipped is broken, and whether they hear it from you or from a customer. The material that moves this round is therefore not the launch, it is the week after. Describe how you knew the change was working, which number you watched and for how long, and what you would have reverted to. A project that ends at the word shipped leaves the manager guessing about the part they care about most.
What to demonstrate
- How a change of yours was verified in production, and whether that check existed before the deploy or was assembled afterwards once something looked wrong
- Whether the rollback story is specific, including the case where a revert is not enough: once a migration has dropped a column or rewritten data, going back is its own change with its own risk
- Whether you can describe an incident you caused, how it was found, and how much time passed between it starting and anyone noticing
- Whether bad news in your account travels early and from you, or consistently arrives via somebody else
How to prepare
- For your last significant change, write down what you watched after the deploy, for how long, and the number that would have made you revert. If nothing was watched, say that plainly instead of inventing a dashboard.
- Work out the rollback answer for a change that touched stored data, and be able to say what made it reversible or what you would have had to do instead. That distinction is a level signal on its own.
- Write the two-minute version of an incident you owned: what broke, how it surfaced, what you did in the first ten minutes, and the change that stopped it recurring. Get the timeline straight, because this is the story most likely to be interrupted with questions.
Technical Panel
reportedA day like this is several different games in a row, and the expensive mistake is carrying the previous one into the next room. Coding rewards narrow precision and finishing inside a timer. Design rewards breadth, stated assumptions and naming what you are deliberately not building. Behavioural rewards specificity about people and decisions. Candidates who over-engineer a coding problem they were supposed to finish, or who start sketching class hierarchies before anyone has agreed what the system has to do, are usually still playing the last round. Between rooms, name out loud which game the next one is.
What to demonstrate
- Whether the coding round ends with something that runs and has been traced against a degenerate input, rather than an extensible design that was never finished
- Whether a design discussion opens by agreeing on traffic shape, read-to-write ratio and what is allowed to be stale, instead of proceeding from an architecture you arrived with
- Whether a behavioural answer names a person, a disagreement and what you did about it, rather than describing the system the story happened inside
- Whether the opening habits still appear late in the day: restating the problem, asking for constraints, saying the plan before typing
How to prepare
- Book three mocks of different types back to back on one afternoon and ask each interviewer afterwards which round you answered in the wrong mode
- Write a three-line opening script per round type — coding: restate, name the approach and its cost, then type; design: ask for scale, read-write mix and what must not break; behavioural: name the person, the stakes and the decision — and run it off a card so the switch is mechanical rather than remembered
- Practise coding with a timer you do not extend, stopping when it stops, so the trained reflex is to finish a correct solution rather than to keep improving one
Onsite Visit
reportedNobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
PracHub editorial advice for the preparation topics above.
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.
Running an EBCDIC-to-ASCII text conversion across a whole fixed-width record coming from a legacy system of record
Those records mix character fields with binary and packed-decimal (COMP-3) numeric fields, where two digits are packed per byte and the sign occupies the low-order nibble of the final byte. A character transcode rewrites those bytes, so an amount does not fail to parse - it parses into a different, entirely plausible number, and the corruption is discovered in reconciliation weeks later or not at all. Parse by field using the record layout: transcode only the character fields, unpack the numeric fields explicitly, and add a checksum or control-total record to the file contract so a mangled batch is rejected rather than posted.
Answering a debugging question with a guess instead of a bisection
Give a procedure that halves the search space at each step: confirm the symptom reproduces, establish the last known-good version, input or timestamp, then bisect over commits, over the data, or over the layers of the request path. A plausible cause with no way to confirm it is the same move whether it happens to be right or wrong, which is why it scores nothing.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Match a rail return file onto payment instructions in one pass
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?
Detect bulk-write bursts in a backdatable case event stream
One day of case_event holds 1.4 million rows, of which about 210,000 carry actor_type='staff' with a non-NULL actor_id. Each row has occurred_at (the asserted time of the act, which staff routinely backdate by days or weeks) and recorded_at (when the row was written). Flag every actor_id that records more than 200 transitions inside any rolling 10-minute window, so an unannounced bulk script can be told from caseworkers clicking. Choose which clock the window slides on, justify it, and state the time and memory bounds.
Approach
- Slide on recorded_at, and separate the two independent reasons. Mechanically, the two-pointer invariant requires the stream key to be non-decreasing so the left pointer only ever advances; rows arrive in recorded_at order, so windowing on occurred_at while consuming them in arrival order breaks that invariant and you would first have to sort the day by occurred_at, an O(n log n) precondition the streaming form does not need. Semantically, even after that sort the occurred_at answer is the wrong one: a script that backdates its writes spreads its asserted times over weeks and fills no window, while the rows it wrote in one second are adjacent in recorded_at. The question is who wrote these rows in a short span, and that is a recorded_at question.
- One pass with a deque of (recorded_at, actor_id) and a hash map from actor_id to in-window count. Advance the right pointer, increment; advance the left pointer past anything older than 10 minutes, decrement, and delete a key at zero so the map stays proportional to active actors. Each event is pushed and popped once: O(n) amortised.
- Emit on threshold crossing, not on every event above it. A single actor holding 900 events in the window would otherwise produce hundreds of near-identical alerts; latch the actor on crossing and clear the latch only when its count falls back below the threshold.
- Memory is the widest window, not the day. Filtering to actor_type='staff' first keeps the deque at the staff rate rather than the 1,800 rows per second the overnight batch reaches; storing 16 bytes per entry rather than the row keeps the worst realistic window inside tens of megabytes. State the bound as a function of peak staff rate times 600 seconds so it survives a change in traffic.
- Refine the discriminator beyond count. Rows written by one statement share a recorded_at to the microsecond, while a caseworker's clicks land seconds apart, so the intra-window timestamp dispersion separates a bulk script from a fast human far more cleanly than volume alone, and it does not need a threshold tuned per unit.
- Keep occurred_at for the separate report it actually answers: the gap between occurred_at and recorded_at is entry lag, and it spikes predictably at reporting boundaries when staff enter a backlog.
Worked solution 25 min
- Build a fixture of 3,000 staff events: actor A writes 240 rows sharing one recorded_at microsecond, actor B writes 260 rows spread evenly across nine minutes, actor C writes 180 rows across eight minutes, and 2,320 unrelated rows scattered across the day.
- Backdate only actor A's occurred_at values, spreading the 240 rows evenly across the previous three weeks — 21 days over 240 rows is one every 126 minutes — and leave recorded_at untouched. Leave occurred_at equal to recorded_at for B, C and the unrelated rows.
- Run the detector on recorded_at. Then sort the same rows by occurred_at, so the second run is a well-defined window rather than an artefact of arrival order, run it on that key, and diff the two flagged sets.
- Compute, for each of A, B and C, the maximum count in any 10-minute window under each key, and the intra-window timestamp dispersion.
Follow-up
- recorded_at defaults to now(), which in this database is transaction start time, so commit order and recorded_at order can differ. What does a reader tailing by recorded_at miss, and how do you bound it?
- A legitimate nightly reassignment job runs under a staff actor_id because it was given a service account with a person's role. How does your detector avoid alerting on it, without an allowlist that grows forever?
- Run this over an unbounded stream rather than a day. What is the new memory bound and what is the first thing that breaks?
Rebuild an award timeline as of a past decision date
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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?
Model a ruleset that is corrected after decisions were issued
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
- 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.
- 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 &&)), withCREATE EXTENSION btree_gistso the equality operator on a scalar can participate in a GiST exclusion constraint. - Be deliberate about bounds.
daterangedefaults to[), so an inclusive statutory end date must be written asdaterange(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. - 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 isgoverns_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 atimestamptzstraight todatesilently uses the session's TimeZone and will move a submission across midnight. - State the rule that makes replay honest: neither query is what you replay against. A determination is replayed against the
rule_version_idrecorded 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. - 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 ondetermination_idand each replay lands on the existing determination through the partial unique index rather than inserting a second one.
Worked solution 45 min
- Create the table with
btree_gistand the three-column exclusion constraint, then insert an original version and confirm a second row overlapping in both axes is rejected. - Insert the retroactive correction — overlapping
governs_period, disjointrecorded_period— and confirm it is accepted, closing the prior row's transaction-time range in the same transaction. - Write both point-in-time queries for a case submitted inside the corrected window and check they return different
rule_version_idvalues. - 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
::datecast. - Run the work-list query over a seeded set of determinations and confirm it returns exactly those decided under the superseded text.
- 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.
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
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
- Partition on the real grain,
(case_id, reopen_cycle). Withcase_idalone, an open cycle'ssubmittedtakes the next cycle'ssubmittedas its LEAD, so a cycle that never closed is reported closed with an interval spanning a cycle boundary. KeepLEAD(event_type)in the output so that is detectable rather than assumed. - Order by
seq, notoccurred_at.seqis unique per case by constraint, so the ordering is total and the result is deterministic.occurred_atis asserted by staff: adeterminedbackdated below its ownsubmittedswaps the two rows, thesubmittedrow 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. - 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 whereevent_type = 'submitted'. WHERE is evaluated before window functions, so filtering to two event types first is both correct and cheap. - Separate pairing from measurement, because
seqonly fixes the first. Ordering byseqguarantees you paired the right two rows; it cannot repair a timestamp a human asserted, so a backdated cycle still yieldsdetermined_at - submitted_atbelow zero. Emit it as computed alongside(determined_at < submitted_at) AS asserted_interval_negative. Do not clamp withGREATEST(..., 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. - 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_atis assigned by the database at insert and appends for one case are serialised — a per-caseseqissued under the case row lock gives you both properties at once. If a case can be appended to concurrently,recorded_atcan invert againstseqtoo and the flag must cover both measures. - Handle the open episode as data, not as a default: when LEAD returns NULL there is no
determinedyet, so emitdetermined_at IS NULLand astill_openflag. Coalescing tonow()silently converts a censored observation into a completed one and drags every average down. - 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 uniqueseqmakes 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
determinedevents 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
withdrawnbetweensubmittedanddeterminedis excluded rather than counted as open.
How would you handle multithreaded programming in a high-performance e…
How would you handle multithreaded programming in a high-performance environment?
Approach
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Can you explain how you would troubleshoot interface issues between yo…
Can you explain how you would troubleshoot interface issues between your code and a teammate’s module?
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Partial form save that survives a three-day gap
Intake renders versioned form definitions and must persist partial submissions that constituents resume days later from a different device, a library machine, or a contact-centre agent's screen. Steady state is 80,000 form sessions a day; the final day of an enrolment window is eight times that, with 30 percent of it in the closing hour. Assume a 25-field form autosaved once per section. Specify the storage grain and key, the peak write rate you design for, how a resumed draft binds to a rule version, and when an abandoned draft is destroyed.
Approach
- Size it before designing it. 640,000 sessions at eight section saves each is 5.1 million writes; spread over a 10-hour active day that is about 140 saves a second, and the closing hour carrying 30 percent of volume is roughly 430 a second. That is a small write rate, so the binding constraint is durability and resumability, not throughput.
- Choose one row per draft holding the whole answer document as JSONB plus a row_version, not one row per field. A section save becomes a single UPDATE ... SET answers = $1, row_version = row_version + 1 WHERE draft_id = $2 AND row_version = $3, so a second open tab affects zero rows and is told it lost, instead of interleaving two versions of one person's answers.
- Make the resume key independent of the browser. Bind the draft to constituent_id when the session is authenticated; otherwise issue a claim code the constituent can write down or receive by SMS. A cookie does not survive a public terminal, and the population this form exists for is disproportionately on public terminals.
- Separate the two versions in play. Pin form_definition_version on the draft so the rendered questions stay stable across the gap, but do not freeze rule_version_id until submission, since the case is adjudicated against the ruleset in force at submission. If the ruleset changes mid-draft, re-render the affected sections and require re-confirmation rather than carrying answers to questions that no longer exist.
- Give the draft its own retention. An abandoned draft holds protected personal data, so assign a retention_class at creation with the clock running from last_touched_at, and destroy on schedule rather than leaving an unsubmitted form in the table forever.
Worked solution 20 min
- Write the DDL for form_draft(draft_id, form_definition_version, constituent_id NULL, claim_code TEXT NULL, answers JSONB, row_version INT NOT NULL, last_touched_at TIMESTAMPTZ, retention_class) with a CHECK that constituent_id or claim_code is non-NULL.
- Derive the peak-day write rate from 640,000 sessions and eight section saves each, stating both the daily mean and the closing-hour figure.
- Write the conditional UPDATE with the row_version predicate and state explicitly what zero rows affected means to the caller.
- Write the submission step: freeze rule_version_id and the evidence snapshot hash, insert the case, and leave the draft in place under its retention class instead of deleting it.
Follow-up
- The form is open in two tabs and the constituent submits from the stale one. What does the server do, and what does the constituent see?
- A draft is saved at 23:58:30 on the closing day and submitted at 00:00:04. Which rule version applies, and is that a code decision or a policy decision?
- The answer document grows to 40 KB. What does each autosave cost in the storage engine, and would you split it?
A slow scanner takes down every document read
An external malware scanner's p99 goes from 80 ms to 30 s while returning no errors; its health check stays green. Within four minutes Document Service answers nothing — including reads of documents already marked scan_status = clean — and Intake Service, which calls it synchronously when a constituent attaches a file, stalls too. Upload handlers call the scanner with no timeout, and a single 200-worker pool serves every endpoint. Offered load is 50 uploads per second. Deliver an ordered checklist, the arithmetic that predicts the collapse, and the fix ordered by blast radius.
Approach
- Separate down from slow first. Error rate flat, latency up, health check green is the harder failure mode precisely because nothing fails fast enough for load shedding to trigger. Look at saturation — workers in use, queue depth, pool wait time — before looking at error rate, because saturation is where the signal is.
- Do the capacity arithmetic with Little's Law. Concurrency equals arrival rate times service time, so 200 workers each held for 30 s serves at most 200/30, about 6.7 requests per second. Offered load is 50 per second, so the pool is fully occupied roughly 200/50 = 4 seconds after the step change and the queue then grows without bound. The four minutes to visible collapse is the scanner's latency ramp, not a slow-building queue.
- Explain the blast radius separately from the cause. Reads of already-clean documents fail because they share one worker pool with uploads; that is a bulkhead defect that exists independently of the scanner and would have been triggered by any slow dependency.
- Fix in blast-radius order. First a per-dependency timeout set well below the caller's own deadline budget, so a stuck call returns a worker instead of holding it. Second, separate pools for read and ingest paths so a saturated ingest path cannot consume the read path's capacity. Third, a circuit breaker on the timeout-and-error ratio so repeated calls stop being attempted at all.
- Treat retries as load amplification: retrying into a saturated dependency multiplies offered load at exactly the wrong moment, so retries need a budget (a token bucket or a fraction-of-traffic cap) and jittered backoff, not just a max-attempts constant.
- Then remove the coupling structurally. Accept the upload, persist with scan_status = pending, scan asynchronously, serve nothing whose verdict is pending. The synchronous call was never required by the contract, and once it is gone, a 30 s scanner degrades throughput of one queue instead of taking down reads.
Follow-up
- What number do you set the timeout to, and which measurement do you derive it from?
- The circuit breaker opens. What does the upload endpoint return, and what does a constituent on a 3 Mbps connection actually see?
- How would you have detected this in the first 30 seconds instead of the fourth minute?
For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Measure before reasoning
- Take a slow piece of your own code, write down in advance where you believe the time goes, then profile it and record how wrong the guess was. The cost is usually an allocation you did not notice or an accidental quadratic membership test.
- Replace one list membership test inside a loop with a set and measure at a thousand, ten thousand and a hundred thousand elements, confirming the shape of the curve rather than only that it got faster.
- Write down the three quantities you can now measure instead of assert: wall time, peak memory, and call count for the function you suspected.
Deliverable: A before-and-after profile of real code plus a written note on the size of the gap between the guess and the measurement.
Practice prompt ↗Practice prompt ↗Worked solution ↗02References, copies, and the bugs they produce
- Write the function with a mutable default argument, call it three times, and explain the accumulating result: the default is evaluated once when the function is defined, so every call shares one object.
- Build a nested structure, take a shallow copy, mutate an inner element, and show that both views changed, because a shallow copy duplicates the container and not the elements. Then fix it with a deep copy and state the cost you just accepted.
- Write two functions, one mutating its argument in place and one rebinding the local name, and predict the caller's view of each before running it. That single distinction produces most of the bugs that pass their tests.
Deliverable: Three small programs whose output you predicted correctly before running, each with a one-line statement of the rule underneath.
Practice prompt ↗Practice prompt ↗03Types, once, in a language that checks them
- Port one module you have already written, roughly a hundred lines, into a statically typed language, and record every place the compiler demanded an answer your original had left implicit: a value that can be absent, a numeric width, a case never handled.
- Write the same signature in both languages and state what the static one guarantees before the program runs and what it does not, since it will not save you from a wrong algorithm or an index out of range.
- Write the difference between an interface satisfied by declaration and one satisfied structurally, with one case each where the other approach would miss the mistake.
Deliverable: One module in two languages plus a list of the questions the type checker forced you to answer.
Practice prompt ↗Practice prompt ↗04Concurrency, starting with what actually runs at the same time
- Run the same CPU-bound function across four threads and four processes and measure both. Under the default CPython build the threaded version will not speed up, because only one thread executes bytecode at a time; the process version will. Check which build you are on first, since free-threaded builds remove that lock and change the result.
- Then run a blocking I/O workload across four threads and measure it speeding up, because the interpreter releases that lock around blocking calls, which is why treating threads as useless is wrong as a general claim.
- Build the lost update: two threads each incrementing a shared counter a hundred thousand times, and show a final value below the expected sum, because an increment is a load, an add and a store and the thread can be suspended between them. Fix it with a lock and then measure what the lock costs.
Deliverable: Three measurements, threads against processes on CPU work, threads on I/O work, and a demonstrated lost update, each with the mechanism written underneath.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Debugging as a procedure rather than an instinct
- Work one real failure as a bisection: find a revision or an input size where it is good and one where it is bad, halve repeatedly, and state the two assumptions bisection needs, that the property changes exactly once across the range and that the test is reliable.
- Minimise one failing input to the smallest version that still fails, and record how many rounds it took.
- Keep a hypothesis log for one bug in three columns, what I believe, what would disprove it, what I observed, and stop yourself the first time you are about to change two things at once.
Deliverable: One bug worked to root cause with a written hypothesis log and a minimised reproducing input.
Practice prompt ↗Practice prompt ↗06Tests that catch the bug you are about to write
- Implement an LRU cache with a capacity bound, then write the three test cases that would catch an off-by-one in eviction: insert exactly capacity items and assert nothing was evicted, insert one more and assert the least recently used key is the one gone, and read an old key just before that insert so the eviction victim changes.
- Add a property test comparing your implementation against a deliberately slow reference, an ordered list scanned linearly, over a few thousand random operation sequences, because a slow reference finds the cases you would not have thought to write.
- Write one numeric test that fails under exact equality and passes with a tolerance, and state why the tolerance has to be relative rather than absolute once the magnitudes grow.
Deliverable: An LRU implementation with three boundary tests, one property test against a slow reference, and one tolerance-based numeric test.
Practice prompt ↗Practice prompt ↗07Debug something broken, out loud
- Have someone plant three defects in a two-hundred-line program, an off-by-one, a shared mutable state bug, and a wrong error-handling path, then find them while narrating, under a fixed rule: state the hypothesis before touching anything.
- Time each one and record which tool found it, reading, a printed value, a debugger, or a test, because the question asked in interviews is how you would find it rather than what it was.
- Write the sentence you will use when you do not yet know the cause, one that names the next measurement instead of offering a guess.
Deliverable: A recorded debugging session with time-to-find per defect and the method that found each.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
When the requirements were thin, the interesting part is how you fenced the problem off: the assumption you wrote down, who you got to confirm it, the narrow version you shipped first so the rest stayed cheap to change. Guessing and being right is luck. Guessing in writing, where someone could correct you, is method.
Describe your experience working in an Agile environment.
Describe your experience working in an Agile environment.
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- 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?
What motivated you to apply to Kratos Defense & Security Solutions?
What motivated you to apply to Kratos Defense & Security Solutions?
Approach
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Tell us about a project you are particularly proud of and your specifi…
Tell us about a project you are particularly proud of and your specific role in its success.
Approach
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
How do you bridge the gap when you are assigned a task involving a tec…
How do you bridge the gap when you are assigned a task involving a technology or domain you haven't mastered yet?
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
- 01
Describe your experience working in an Agile environment.
- 02
What motivated you to apply to Kratos Defense & Security Solutions?
- 03
Tell us about a project you are particularly proud of and your specific role in its success.
- 04
How do you bridge the gap when you are assigned a task involving a technology or domain you haven't mastered yet?
Is this an official Kratos Defense & Security Solutions interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Kratos Defense & Security Solutions. Rounds and questions reflect what candidates have reported, not a process Kratos Defense & Security Solutions has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the hiring process usually take?
The timeline can range from a few weeks to several months, depending on the project’s scope and security requirements. Kratos aims to maintain prompt communication at every stage.
PracHub interview research ↗What is the best way to prepare for the technical portion?
Focus on your previous projects. Be prepared to explain the technical challenges you faced and how you solved them, as the interviews prioritize real-world experience over abstract puzzles.
PracHub interview research ↗Is there a specific culture I should be aware of?
Kratos Defense & Security Solutions values mission-driven work and long-term stability. Its teams are often made up of experienced engineers who appreciate clear, direct, and professional communication.
PracHub interview research ↗Are there opportunities for professional growth?
Yes, Kratos encourages continuous learning and internal mobility. It looks for engineers who are interested in taking on increasing levels of responsibility as they master its specific domain.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22