cswork / libraryOpen practice workspace

Interview guides · ISL Engineering and Land Services

ISL Engineering and Land Services Software Engineer Interview Questions & Guide 2026

ISL Engineering and Land Services software engineer prep on operations: decide what you would need to see to know a release is going wrong.

The 60-second brief

At ISL Engineering and Land Services, the Software Engineer role is a critical function that bridges the gap between complex infrastructure design and modern digital solutions. Beyond writing code, you build the tools and systems that ISL Engineering and Land Services' multidisciplinary teams use to deliver projects ranging from municipal planning to electrical and control systems for infrastructure.

Treat capacity estimation as a conversion skill rather than a table to memorise: turn a user count and an action rate into requests per second and bytes per day, then name the component that number breaks first. The figure only matters if it changes the design.

Focus areas

  • Evolve schemas expand-contract across un-upgradable client deployments
  • Diagnose failures inside environments you cannot instrument
  • Version an API you cannot force clients to upgrade

Overview

Your work directly influences the efficiency of the firm's engineering workflows and the quality of the environments its projects help create. Whether you are automating design processes, managing data-driven infrastructure projects, or developing specialized software for control systems, your technical contributions are essential to the firm's operational success. You will operate in a collaborative, cross-functional environment where your ability to translate engineering requirements into robust software solutions is highly valued.

ISL Engineering and Land Services prioritizes a culture of collaboration; expect to work closely with civil, electrical, and structural engineers who may not have a software background. Your ability to communicate technical concepts clearly is just as important as your coding proficiency.

The ISL Engineering and Land Services interview process

ISL Engineering and Land Services candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

High-Level Discussion

An unlabelled round is first an information problem, and the cheapest information is free. Whoever schedules it can usually tell you how long it runs, who will be in the room and what they work on, whether you will be writing code and in what environment, and whether anything is being sent beforehand. Ask in writing so the answer is on record, then prepare for the two or three formats those answers still leave open instead of betting on one. What separates a strong candidate is not guessing right; it is having an opening that works whichever one it turns out to be.

What to demonstrate

  • Whether you can start work from an ambiguous brief, since tolerating a vague scope without stalling is the same thing the job asks for
  • Whether the questions you asked beforehand were ones that change your preparation, such as duration, medium and who is joining, rather than ones whose answers you could not have acted on
  • Whether you adapt when the round turns out to be something other than what you were told, instead of spending the first ten minutes visibly recalibrating

How to prepare

  • Send one short scheduling message asking four things: how long, who is joining and what they work on, whether you will be writing code and where, and whether to prepare anything in advance. Treat a vague reply as real information, since it means the round is loosely structured and you will be shaping it yourself.
  • Write one opening that works in any of the formats still open: restate in your own words what you have been asked to do, then ask which of two directions is more useful to them. Say it aloud until it stops sounding recited.
  • Set up for the two most likely formats before the call starts, with a blank editor in the language you would choose and a shared document you can type into, so a format surprise costs you nothing in the first minutes

Technical Meetings

Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.

What to demonstrate

  • Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
  • Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
  • Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
  • Whether a failing case is isolated and explained before any edit is made to the code

How to prepare

  • From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
  • Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
  • Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.

Design Presentation

You cannot drill a format you do not know, so put the preparation into material that travels. Three pieces of your own work, each rehearsed until you can take a follow-up you did not anticipate, will carry a conversation or a code walkthrough equally well. Specificity is what separates that from filler. A number needs its definition before it means anything: a p99 is over some window and measured at some hop, and a server-side figure excludes the queueing and network time a client would see. The number you cannot qualify is the one to leave out.

What to demonstrate

  • Whether your examples carry detail only someone who did the work would hold, such as what the binding constraint actually was, which alternative you rejected and why it was worse, and what you measured on each side of the change
  • Whether a number survives one follow-up, meaning you can say what it was measured over and whether it moved because of your change or merely alongside it
  • Whether a failure is described with the specific change that followed it, rather than a lesson stated in general terms
  • Whether your part in a team effort is stated accurately, including what other people did

How to prepare

  • Write a page on each of three projects covering the constraint, the option you rejected, the measurement before and after, and what went wrong. Cut any line you cannot take a follow-up on, since you are writing the parts you will be pressed on rather than a summary.
  • Recover the real figures while you still have access: request volume, data size, latency with its percentile and window, team size, timeline. Note where each came from, whether a dashboard, a design document or memory, and mark the estimates so you can say which they are out loud.
  • Take your weakest project story to someone who works in a different area and have them ask why four times in succession. The point where you run out of answer is the part to go and re-read before the round.

Team Interaction

When a round has no standard shape, it is often there because something is still open: an area no earlier conversation reached, a round where the signal came out mixed, or a decision someone is not ready to make alone. Work out which by going back over what each earlier round actually covered rather than how it felt, and arrive able to give evidence on that point without being asked twice. Weak answers replay the loop's earlier material at the same depth. Strong ones go a level deeper and stay consistent with what you already said.

What to demonstrate

  • Whether your account of a project matches the one you gave earlier in the loop, since what you said before may be available to whoever runs this round
  • Whether you can go a level deeper on something already covered, reaching the decision and its alternatives rather than repeating the summary
  • Whether you state your own uncertainty accurately, including parts of a system you did not build and decisions you inherited, instead of claiming even ownership across all of it
  • Whether you can answer a question you handled poorly earlier by naming what you missed, rather than delivering a polished second version as if the first had not happened

How to prepare

  • Reconstruct the loop on one page: for each round, the questions you were asked and the answer you actually gave, not the better one you thought of afterwards. The gaps on that page are your best available guess at why this round exists.
  • Take the two claims you made earlier that carry the most weight and assemble the backing for each: the measurement, the date, what broke, the decision you would make differently now.
  • Write down the three facts about your work that must not drift between tellings, such as team size, timeline and your own role, and check your stories against that list rather than trusting recall under pressure

Where candidates lose points

Validating against a client sandbox and assuming production parity

Sandboxes typically carry smaller data, looser or absent rate limits, a schema version behind production, and sometimes synchronous behaviour where production is asynchronous. Code that passes there fails first in the client's production, during a change window you do not control and often cannot get a second one of. The mitigations are specific: assert the observed schema version at the boundary of every run, measure the production rate limit empirically rather than reading it from a document, and design the first production run to be a bounded, reversible slice rather than a full backfill.

Tenant context leaking across a connection pool

Setting the tenant on a connection and relying on it for the rest of the request looks correct in every test that runs one request at a time. Under transaction pooling the connection goes back to the pool with that session state still attached, and the next checkout, serving a different client, inherits it; row-level security then enforces the previous tenant's policy flawlessly and returns the wrong rows. It reproduces only under concurrency, which is the load profile your integration tests do not have. Use a transaction-scoped setting, and assert that the setting matches the request's tenant immediately before the first query rather than trusting that it was set correctly upstream.

Sorting when the problem never required a total order

Match the algorithm to the guarantee actually needed: the top k comes from a size-k heap in O(n log k) time and O(k) space, distinctness needs a set rather than an ordering, and a small bounded integer key range admits a linear counting pass. A full O(n log n) sort is the right default only when you genuinely need everything in order.

Listing technologies instead of trade-offs

Name the property the design needs first, such as ordered range scans, multi-entity transactions, cheap appends, or a predictable p99, then pick something that provides it and say what it gives up in exchange. Almost any component is defensible once you state the requirement it satisfies and the one it sacrifices.

Questions & practice

Compute the minimal re-run set that preserves idempotency keys

Hard · interval complement · idempotency keys · watermarks

One connector binding must cover [span_start, span_end). You have all its connector_run rows: (idempotency_key, attempt_no, status, watermark_from, watermark_to), with half-open ranges, where idempotency_key is derived deterministically from the connector and the range, so re-running a range reproduces its key. Only status='succeeded' counts as covered. Produce the minimal re-run plan: every instant in the span ends up covered, any sub-interval some prior run already claimed is re-run at exactly that claim's range, and genuinely unclaimed sub-intervals become new claims cut at the binding's chunk size. Explain what merging ranges would cost.

Approach

  1. Merge the succeeded ranges into a maximal disjoint union: sort by start, sweep extending the open segment while the next start is not beyond the current end. O(n log n) time, O(n) space, and this is the only status that contributes coverage.
  2. Take the complement of that union inside [span_start, span_end) to get the uncovered gaps, in O(u) over the merged segments. Clip at both ends so the plan never reaches outside the span.
  3. Sweep the non-succeeded claims (ambiguous, failed, abandoned) against the gap list with a two-pointer over both sorted lists. Any claim intersecting a gap is emitted at its original watermark_from and watermark_to, byte for byte, so the derived idempotency_key reproduces and the retry rejoins the same ledger row. O(n) after sorting.
  4. Subtract the emitted claim ranges from the gaps; whatever survives was never claimed by any attempt and becomes fresh ranges cut at the chunk size. Only these get new keys. O(u + c).
  5. Accept that a re-emitted claim may overlap time that is already succeeded. Range bookkeeping is not what makes the retry safe; the record-level dedupe key is, and re-reading a covered window costs one extra fetch against the client's quota, which is far cheaper than a key you can no longer reproduce.
  6. Exclude anything still in status 'running' with a live lease. Re-claiming it races the in-flight attempt, doubles the quota spend against the client endpoint, and produces two ledger writers for one key. Total plan cost is O(n log n) time and O(n) space.

Follow-up

  • An ambiguous run may have applied every record before the timeout. What do you read to settle it before re-running, and what is left if the client's schema offers no natural key?
  • Retention pruned the ledger rows for one of these keys. What does the re-run do now, and what bounds how long the ledger must be kept?
  • Two planners run concurrently against the same binding. What stops them emitting and executing the same plan twice?

Assign connector bindings to rollout waves and name the cycle

Medium · topological sort · cycle detection · dependency graph

Connector bindings declare ordering dependencies: an edge u -> v means v must not run in a wave earlier than one after u has succeeded. You have up to 50,000 bindings and 200,000 edges, and a misconfigured dependency may have created a cycle. Assign each binding the earliest wave number such that every predecessor sits in a strictly earlier wave, return the total wave count, and if a cycle exists return the bindings on one concrete cycle rather than a boolean. The implementation must be iterative: a dependency chain can be 50,000 deep.

Approach

  1. Build an adjacency list and an indegree array, then run Kahn's algorithm from every indegree-zero node. Bindings with no predecessors get wave 0. O(V+E) time, O(V+E) space.
  2. Relax waves as you decrement: when popping u, set wave[v] = max(wave[v], wave[u]+1) for each successor before enqueueing v at indegree zero. Because v is only enqueued after its last predecessor is popped, wave[v] is 1 + max over predecessors, which is the earliest legal wave. Total wave count is max(wave)+1, the longest path in nodes.
  3. If Kahn emits fewer than V nodes, the residual subgraph is exactly the nodes lying on or downstream of a cycle. Every residual node still has an in-edge inside the residual, otherwise it would have been popped, so walking backwards along in-edges cannot dead-end and must revisit a node within |residual| steps; the segment between the two visits is a concrete cycle. O(V) with a visited-position map.
  4. Use an explicit stack or a queue rather than recursion. Recursive DFS on a 50,000-node chain exceeds the default stack in most runtimes, and the failure looks like a crash rather than a cycle.
  5. At this size, recompute the whole plan whenever an edge changes. Incremental topological order maintenance costs far more code than the O(V+E) rebuild it saves.

Follow-up

  • Two bindings land in the same wave but hit the same client endpoint, whose rate budget is shared. How does the wave plan have to change, and what problem class does that turn into?
  • One dependency holds only for overlapping watermark ranges rather than globally. Is the dependency graph still a DAG over bindings, and what is the right vertex if not?
  • Report the critical path so an operator can see which chain sets the wave count, not just that it is five.

Compare release versions and report the live estate spread

Easy · parsing · version skew · ordering

Each environment row carries reported_release as TEXT: either NULL, or a version of the form MAJOR.MINOR.PATCH with an optional -rc.N suffix. Up to 400 environments. Ordering rule: compare major, then minor, then patch numerically; a -rc.N build precedes the same MAJOR.MINOR.PATCH with no suffix, and rc numbers compare numerically. Return the lowest and highest reported versions, the minor-version spread between them, and the count of environments whose reported_release is NULL. NULL means the environment's version is unknown, not that it converged.

Approach

  1. Parse each string once into a comparable tuple: (major, minor, patch, is_final, rc), where is_final is 1 for a plain release and 0 for a pre-release, and rc is 0 when absent. Tuple comparison then yields the stated order for free, including 3.10.0-rc.1 < 3.10.0. O(L) per string, O(n*L) total.
  2. Track min and max in one linear scan rather than sorting: O(n) time, O(1) extra space, and n is only a few hundred so the parse dominates either way.
  3. Fail loudly on a string that does not match the grammar instead of defaulting it to 0.0.0 or to the newest release. An unparseable reported_release is a reconciliation-ingest bug, and either default hides it in the direction that looks safe.
  4. Compute the minor spread only when min and max share a major version. Across majors the difference of minor numbers is meaningless, so return the pair of versions and flag the condition rather than a number.
  5. Report the NULL count as its own output line. Folding unknowns into 'converged' is how an install ends up running a release nobody knew was still live.

Worked solution (15 min)

  1. Input reported_release values: '3.9.0', '3.10.2', '3.10.0-rc.1', NULL, '3.9.0'.
  2. Parse to tuples: (3,9,0,1,0), (3,10,2,1,0), (3,10,0,0,1). The NULL is diverted to the unknown counter before parsing.
  3. Scan for min and max by tuple: min is (3,9,0,1,0) = 3.9.0, max is (3,10,2,1,0) = 3.10.2. Confirm 3.10.0-rc.1 sits strictly between them.
  4. Same major (3), so the minor spread is 10 - 9 = 1. Unknown count is 1.

Expected result: lowest='3.9.0', highest='3.10.2', minor_spread=1, unknown_count=1. A lexicographic sort of the same list would have returned highest='3.9.0' and lowest='3.10.0-rc.1'.

Verify it yourself

  • The parsed tuple for '3.10.0-rc.1' compares less than the tuple for '3.10.0' and greater than the tuple for '3.9.0'.
  • unknown_count plus the number of parsed versions equals the number of environment rows, so nothing was dropped.
  • Swapping the input order does not change any of the four outputs.
  • Adding a second environment already at '3.9.0' leaves the spread at 1, since spread is over distinct versions present, not over row counts.

Follow-up

  • Two majors are live at once. What does 'spread' mean now, and what single number, if any, would you still publish?
  • reported_release is a claim from the environment, and last_heartbeat_at may be days old for a client-managed install. At what staleness does this figure stop being usable?
  • How would you enforce the supported window when the rollout plan is built, instead of reporting a violation after it already exists?

Add a non-null column to a live work-record table

Hard · online migration · lock contention · expand-contract

work_record holds roughly nine million rows and absorbs a month-end write burst. You must add currency CHAR(3) NOT NULL, backfill it from the engagement, add a foreign key to rate_card, and eventually drop the superseded hours_decimal column. Several releases are live, including client-managed installs running two-quarter-old code that still writes hours_decimal. Give the ordered plan, the lock each step takes and for roughly how long, how the backfill is chunked and restarted, and the signal that makes the drop safe.

Approach

  1. Sequence it expand, dual-write, backfill, switch reads, contract, and be explicit that only the last step is dangerous. Adding is cheap because an old release ignores a column it does not know about; removing breaks a writer you cannot upgrade and whose failure you may not see for weeks.
  2. ALTER TABLE work_record ADD COLUMN currency CHAR(3) — nullable, no default. That is a catalog-only change on every supported version and takes milliseconds. Refuse NOT NULL DEFAULT 'XXX': PostgreSQL 11 and later store a non-volatile default in the catalog and materialise it on read, so it is just as fast, but it hands all nine million rows a value that is already non-null and already wrong. That destroys the rest of the plan — the NOT NULL you add later is satisfied from the first second, so it can never find a row the backfill missed, and every consumer now has to distinguish a real currency from a sentinel forever. A NULL is the only marker that survives a crashed backfill.
  3. The statement is instant; the lock queue is not. An ACCESS EXCLUSIVE request waits behind every open transaction holding a conflicting lock on the table, and every request arriving after it queues behind that, so one long analytics SELECT turns instant DDL into a total stall. Run it with SET lock_timeout = '2s' inside a retry loop that catches 55P03 and backs off with jitter.
  4. Decide up front what an unconverged writer inserts. Two-quarter-old code omits currency, so it writes NULL, which means SET NOT NULL would start rejecting its inserts outright. Either fill the gap in the database with a BEFORE INSERT trigger that resolves currency from the engagement when NEW.currency IS NULL — one indexed lookup per insert, which you should time against the month-end burst before committing to it — or leave the column nullable and gate SET NOT NULL on the same release-convergence signal that gates the drop. What you must not do is paper over it with a default, which turns a visible NULL from an unconverged writer into an invisible 'XXX' that reconciliation will never flag.
  5. Backfill on a keyset cursor — WHERE work_record_id > $last AND currency IS NULL ORDER BY work_record_id LIMIT 5000 — one transaction per batch, advancing $last to the last work_record_id the batch wrote so filled rows are never revisited. Set statement_timeout, pause between batches so autovacuum and any replicas keep up, and persist the cursor so a crash resumes from the last committed batch. OFFSET n costs O(n) per page because the server scans and discards those rows, which makes the whole backfill quadratic in the table size.
  6. Take NOT NULL without a blocking scan, and only once the backfill reports zero rows remaining: ADD CONSTRAINT work_record_currency_nn CHECK (currency IS NOT NULL) NOT VALID takes a brief ACCESS EXCLUSIVE and scans nothing; VALIDATE CONSTRAINT runs under SHARE UPDATE EXCLUSIVE so reads and writes continue throughout, and raises check_violation, SQLSTATE 23514, naming a row if the backfill missed one — that failure is the entire reason the step exists, and it only exists because the column was added without a default; then SET NOT NULL, which on PostgreSQL 12 and later proves itself from the validated CHECK and skips its own full scan.
  7. Use the same shape for the foreign key — ADD CONSTRAINT ... REFERENCES rate_card(...) NOT VALID, then VALIDATE CONSTRAINT — and build any supporting index with CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block, scans the table twice, and on failure leaves an INVALID index that must be dropped concurrently before you retry. Note what the FK does not prove: a single-column reference is MATCH SIMPLE, so rows with NULL currency pass validation untouched, and validating it before the backfill finishes certifies only the rows that were already correct.
  8. Gate the contract step on observed convergence rather than elapsed time: every non-destroyed environment must report a reported_release_id at or past the release that stopped writing hours_decimal, and reported_release_id IS NULL counts as unknown and therefore blocks. DROP COLUMN itself is a catalog-only update, instant under ACCESS EXCLUSIVE, reclaiming no space until a rewrite — the cost is entirely in the writer you did not know was still running.

Worked solution (45 min)

  1. Write out each DDL statement in order, annotated with its lock mode and whether it is safe during the month-end burst.
  2. Implement the retry wrapper: SET lock_timeout, attempt, catch 55P03, sleep with jitter, retry a bounded number of times.
  3. Write the keyset backfill loop with the cursor persisted after each committed batch, and time one batch.
  4. Leave one row un-backfilled on purpose, run VALIDATE CONSTRAINT, and read the 23514 it raises — then fix that row and re-run. A plan in which this step cannot fail is a plan that is not checking anything.
  5. Write the convergence query over environment that returns the rows still blocking the drop.
  6. Ship the DROP COLUMN as its own migration whose precondition is that query returning zero rows.

Expected result: An ordered plan: ADD COLUMN currency CHAR(3) nullable with no default, under a short ACCESS EXCLUSIVE with lock_timeout and retry; a BEFORE INSERT trigger or a convergence gate covering writers that do not yet set it; application dual-write; keyset backfill in bounded, restartable batches until no NULLs remain; CHECK ... NOT VALID then VALIDATE, which fails loudly on a missed row, then SET NOT NULL; FK NOT VALID then VALIDATE with any index built CONCURRENTLY; reads switched; DROP COLUMN withheld until every non-destroyed environment reports a converged release.

Verify it yourself

  • Hold an open transaction with a SELECT on work_record, issue the ALTER, and watch the queue form in pg_locks and pg_stat_activity; then confirm lock_timeout aborts the ALTER instead of everything else waiting on it.
  • SELECT count(*) FROM work_record WHERE currency IS NULL falls batch by batch and must read zero before SET NOT NULL; under a sentinel default it would have read zero from the first minute and proved nothing.
  • Time one batch and multiply: nine million rows at 5,000 per batch is 1,800 batches, so the inter-batch pause, not the query, sets the wall-clock duration.
  • Run concurrent inserts throughout VALIDATE CONSTRAINT and confirm none of them blocked.
  • With the trigger disabled, an insert from a simulated old release lands with currency NULL and is caught by the NULL count rather than passing as a plausible value.
  • The convergence query returns non-zero while any environment has reported_release_id IS NULL, so a silent client-managed install blocks the drop rather than being assumed converged.

Follow-up

  • A client-managed install reports a release you no longer support and has not converged for two quarters. Do you drop, and what do you owe that client first?
  • During the dual-write window, which column does the month-end reconciliation read, and what does a mismatch between the two mean?
  • The backfill dies at batch 900 of 1,800. What exactly does the restart re-read, and how do you know it did not skip a range?

Explain why the failed-run backlog query ignores three indexes

Medium · indexing · query planning · partial indexes

connector_run holds roughly six million rows over three years. Its indexes are (connector_id, started_at DESC); (started_at) WHERE status = 'failed'; and ((finished_at AT TIME ZONE 'UTC')::date). The operator backlog query is SELECT run_id, connector_id, failure_class, started_at FROM connector_run WHERE environment_id = $1 AND status = ANY($2) AND started_at >= now() - interval '7 days' ORDER BY started_at DESC LIMIT 50, with $2 bound to {failed, ambiguous}. It sequentially scans. Explain index by index why each is unusable, then give the one index you would add.

Approach

  1. Index one leads on connector_id, which this query never constrains. A B-tree can only be probed from a prefix of its key columns, so with no equality on connector_id it degenerates into a full index scan plus heap fetches, which the planner will rightly cost above a sequential scan. PostgreSQL 18 added B-tree skip scan for an unconstrained prefix, so state your server version before assuming this is fatal; it helps only when that prefix has few distinct values, and hundreds of connectors is already too many to rely on it.
  2. Index two is partial on status = 'failed', and the rows the query wants are partly not in it at all. Predicate implication runs one way: the query's predicate must imply the index's. status IN ('failed','ambiguous') is weaker than status = 'failed' and implies nothing, so no amount of inlining the parameter helps. It would serve only the failed branch of a UNION ALL.
  3. Index three is an expression index on a function of finished_at, while the query filters started_at as a plain range. Even on the right column, a range predicate on the bare column cannot use an index on a function of it. Note also why the AT TIME ZONE 'UTC' wrapper is present: finished_at::date on a timestamptz is STABLE rather than IMMUTABLE because it depends on the session TimeZone, so PostgreSQL refuses to index it without a fixed zone.
  4. Add (environment_id, started_at DESC) WHERE status IN ('failed','ambiguous'): equality on the scoping column first so the scan starts in the right place, the ordering column second so ORDER BY started_at DESC LIMIT 50 stops after fifty index entries instead of sorting the week, and partial on the status set so the index covers the small failing minority rather than six million rows.
  5. Confirm the partial predicate is provable for the query's parameterised form. Under a custom plan the array parameter is substituted as a constant and the prover handles the ScalarArrayOpExpr; under a generic plan it stays a Param and the proof fails, so check plan_cache_mode or the sixth execution of a prepared statement before declaring victory.
  6. Say what you deliberately did not do. Adding INCLUDE (run_id, connector_id, failure_class) to chase an index-only scan pays only while the visibility map is current, which ties the query's latency to whether autovacuum is keeping up with the write rate — a dependency worth accepting consciously, not by accident.

Follow-up

  • The same query is also run without the environment filter, for an estate-wide backlog. Does your index still help, and what would you add instead?
  • How does row-level security on this table interact with your index, given that policy quals are applied before non-leakproof user quals?
  • Six months from now the partial index covers 30 percent of the table. What signal tells you to change it?

Present your approach to the provided technical assignment and explain your design choices.

Medium

Approach

  1. State your assumptions explicitly before working the problem.
  2. Work from the requirement backwards to the design.
  3. Say what you would check first and why it is the highest-information step.

Follow-up

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

How do you ensure your software solutions integrate effectively with existing infrastructure or control systems?

Medium

Approach

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

Follow-up

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

Design a run API where the outcome is genuinely unknown

Hard · idempotency · state-machines · ambiguity · at-least-once

connector_run holds run_id, connector_id, environment_id, idempotency_key, attempt_no, status, failure_class, watermark_from, watermark_to, records_read, records_applied, source_schema_version, started_at and finished_at; idempotency_key is derived from the connector and the watermark range, and (connector_id, idempotency_key, attempt_no) is unique. Workers claim a range, write to a client-owned target that offers no idempotency support of its own, then report back. A worker can die at any point, and a write can time out leaving the target's state unknown. Design the claim, report and settle endpoints and the status transitions a scheduler and an operator drive.

Approach

  1. Derive the run's identity from the work rather than from the caller. POST /connectors/{id}/runs carrying a watermark range computes idempotency_key deterministically, so a scheduler retrying a claim receives the same logical run instead of a second one covering the same rows. Mint a new attempt_no only on an explicit retry of an attempt that has already settled as failed, which keeps the attempt history readable and stops a crashed worker from quietly opening a parallel attempt over the same range.
  2. Give the claim a lease with an expiry rather than a lock. The worker heartbeats; an expired lease makes the run reclaimable. Without an expiry, a dead worker wedges the binding until someone notices. With a lease and no fencing, two workers can both believe they hold the range, so carry a monotonically increasing lease token and reject any settle whose token is stale — that is what stops a zombie's late report overwriting the live attempt's.
  3. Make ambiguous a first-class status with its own transitions, separate from failed. A timeout, or a reset after the request bytes went out, leaves the target's state unknown: settling as failed and retrying duplicates, settling as succeeded and advancing the watermark loses data silently. The settle endpoint therefore accepts succeeded, failed with a class, or ambiguous with evidence, and only succeeded advances the watermark.
  4. Keep the ledger write and the watermark advance in one local transaction, and have settle carry the dedupe keys actually applied. The target write and your commit cannot be made atomic without a distributed transaction no client will grant, so ambiguity is resolved by a reconciliation read: re-read the target by dedupe key over the claimed range and settle from what you find. Store the key's input fields and a derivation version alongside the hash, because the client will eventually rename or retype one of those fields and every previously computed key still has to be reproducible.
  5. Define each caller's behaviour precisely. The scheduler re-claims expired leases and retries only definite failures whose class is retryable; it never retries an ambiguous run. The operator has an explicit resolve endpoint used after reconciliation, recording who decided and on what evidence. An ambiguous run that auto-retries is the design bug — it converts an unknown into a duplicate inside a system you do not own and cannot correct.
  6. Close the loop with the counters. records_applied differs from records_read by the dedupe hit count, so a retry over a partially applied range should report a high hit count and little net change. That difference is the observable proving the dedupe key works: if a retry reports applied equal to read, the key is not matching and you are duplicating on the client's side right now.

Worked solution (40 min)

  1. Write the state machine — queued, running, succeeded, failed, ambiguous, cancelled, abandoned — with every legal transition and who triggers it.
  2. Write the claim endpoint's request and response, showing how idempotency_key is derived and what a repeated claim returns.
  3. Inject a crash between each adjacent pair of claim, fetch credential, validate, write, ledger, advance, and say what the next claim of that range does in each case.
  4. Write the settle payload for the ambiguous case, listing the evidence a later reconciliation will need.
  5. Trace a zombie worker settling ninety seconds after its lease expired, and show the token check rejecting it.
  6. Write the operator's resolve endpoint and the audit fields it records.

Expected result: A claim keyed on (connector_id, watermark range) that is safe to repeat, a leased and heartbeat-renewed claim with a fencing token checked at settle, a settle endpoint accepting succeeded, failed-with-class or ambiguous-with-evidence where only succeeded advances the watermark, a reconciliation path that resolves ambiguity by reading the target back by dedupe key, and an operator resolve step that never happens automatically.

Verify it yourself

  • Retrying a claim for the same range never produces two runs with different idempotency_keys.
  • A run left ambiguous stays ambiguous until something reads the target; nothing in the scheduler moves it.
  • Replaying a fully applied range reports records_applied of zero with a dedupe hit count equal to records_read.

Follow-up

  • The client's endpoint offers no read-back by your dedupe key. How do you settle an ambiguous run then?
  • A worker's lease expires while its write is still in flight and a second worker claims the range. What exactly prevents the double apply?
  • How long do you keep the dedupe ledger, and what happens when a record arrives after its ledger row was pruned?

One connector burns a client's entire rate budget

Easy · poison message · retry loop · rate limiting

A client reports that your integration is exhausting their API quota, and every connector binding for that client now fails with client_429. For one binding, connector_run holds 300 rows sharing one idempotency_key with attempt_no incrementing, all with status 'running', finished_at NULL and failure_class NULL, created exactly 120 seconds apart over the last ten hours. The run lease is 120 seconds. Workers in that pool show restart counts in the hundreds. Other clients are unaffected. Diagnose in order, say who is at fault, and state what stops one range doing this again.

Approach

  1. Split client fault from your fault before anyone contacts the client. Group the last hour's failures by client and by binding: every binding for one client failing while other clients are clean means a resource shared with that client is exhausted, and the only thing you share with them is their quota.
  2. Read the ledger's shape, which is the entire diagnosis. Three hundred attempts under one idempotency_key, none with a terminal status and none with a failure_class, spaced at exactly the lease interval. A handled failure writes failure_class and finished_at; writing nothing at all means the worker was killed before it could classify. The exact spacing names the killer as lease expiry, not the client endpoint.
  3. Quantify the damage from the same rows: records_applied is zero across all 300, while each attempt got far enough to pull pages. The binding consumes quota continuously and applies nothing, which is why the other bindings for that client see 429 and the faulty one does not.
  4. Reproduce offline. watermark_from and watermark_to are recorded, so replay that exact range against a captured payload with the lease disabled and find what exceeds 120 seconds: a page far larger than the others, a validator input that degrades badly, or an unbounded in-memory accumulation over the range.
  5. Fix the loop before the payload. Cap attempt_no, move the range to status 'abandoned' with an alert, and hold the watermark rather than advancing past it, because a silent gap is data loss and skipping must require a human decision. Then make the work fit the lease: stream and chunk the page, renew the lease only while progress is provable, and bound the page size at the boundary.
  6. Add the detector this evaded. Alert on attempts that never reach a terminal state, not only on failure counts, because a run that never finishes never increments any failure metric.

Follow-up

  • The client asks how much of their quota you consumed. Which rows answer that, and what would you have to add to answer it exactly?
  • The abandoned range contains records you still need. What is the safe way to get past it without breaking the at-most-once guarantee?
  • Lease renewal introduces a new failure: a worker that renews steadily but makes no progress. How do you catch that one?

Suggested practice checklist

Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.

Day 1 — Diagnostic, scored before you study anything

  • Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
  • Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
  • Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.

Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.

Day 2 — Largest gap: find the boundary rather than the subject

  • Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
  • Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
  • Re-attempt one of them from blank four hours later with nothing open.

Deliverable: A sub-skill map with the two blocking sub-skills circled.

Day 3 — Drill the blocking sub-skill by repeating the shape

  • Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
  • State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
  • Have someone else read your one-sentence rule and find the precondition you left out.

Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.

Day 4 — Second gap, plus maintenance on the strongest area

  • Run the same sub-skill decomposition on the second-largest gap in half the time.
  • Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
  • Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.

Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.

Day 5 — The gap that is not a skill

  • Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
  • Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
  • Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.

Deliverable: Two recordings with a counted reduction in time-to-first-question.

Day 6 — Retest under day-one conditions

  • Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
  • For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
  • Write down which single block you would still lose the offer on.

Deliverable: A second scored rubric placed beside the first, with one named remaining risk.

Day 7 — Full loop under interview conditions

  • Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
  • Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
  • Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.

Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.

Bounding a vague ticket before writing code

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 a time you had to collaborate with non-technical stakeholders to deliver a project.

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  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 would you do differently if you ran that again?
  • How did you know your change caused the improvement?

How do you handle feedback on your design or code during a peer review?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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

Where do you see yourself in five years within the engineering and technology space?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Close with what you would do differently, concretely.
  3. Name the disagreement and how you resolved it with evidence.

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 troubleshoot a complex technical issue during a project lifecycle.

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  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 would you do differently if you ran that again?
  • How did you know your change caused the improvement?