cswork / libraryOpen practice workspace

Interview guides · Conative IT Solutions

Conative IT Solutions Software Engineer Interview Questions & Guide 2026

Concurrency drills for Conative IT Solutions software engineer prep: races, lock ordering, and what a check-then-act sequence assumes.

The 60-second brief

A Software Engineer at Conative IT Solutions plays a pivotal role in the delivery of digital services, bridging the gap between complex client requirements and functional, user-centric web solutions. You are expected to be a versatile developer who can navigate the full lifecycle of a project, from initial interface design to the rigorous validation of features.

Getting the code to run is the floor. What usually separates answers is the case checked without prompting: empty input, a single element, duplicate keys, or a value that overflows the integer type you chose.

Focus areas

  • Make integration retries idempotent without target-side idempotency keys
  • Diagnose failures inside environments you cannot instrument
  • Scope every query by tenant below application code

Overview

This role is critical to the company’s ability to maintain its reputation for delivering reliable software. You will contribute directly to the development of web interfaces and ensure high-quality output through both coding and manual testing processes. Success in this position requires a blend of technical proficiency in core web technologies and the ability to articulate your problem-solving process to leadership, as the firm places a high value on clear communication and project ownership.

The Conative IT Solutions interview process

Conative IT Solutions candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Administrative Formalities

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.

Written Evaluations

What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.

What to demonstrate

  • Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
  • Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
  • Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly

How to prepare

  • Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
  • Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
  • Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live

Technical Rounds

The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.

What to demonstrate

  • In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
  • In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
  • Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes

How to prepare

  • Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
  • Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
  • For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first

Final Leadership Review

Where the day includes a partner from product, design or data, that conversation is weighted like the technical ones and prepared for least. They are deciding one thing: whether having you in the room makes their decisions cheaper. That means options with costs attached, not implementation detail and not "it depends". An estimate someone can plan against — a range, the assumption that would push it to the high end, and what you would drop to hit the low one — is worth more than a confident single number, which everyone present already knows is wrong.

What to demonstrate

  • Whether an estimate comes as a range with the assumption most likely to break it, and states what a specific scope cut would actually buy
  • Whether a technical constraint is handed over as a choice with consequences on their side, rather than as a verdict they have no standing to argue with
  • Whether you establish what decision is on the table before proposing anything
  • Whether risk is raised while it can still change the plan, with the trigger that would confirm it, instead of reported afterwards as a slip

How to prepare

  • Take a project that shipped late and write the two-sentence warning you could have given three weeks earlier, naming what you would have needed decided at that point
  • Rehearse one estimate out loud until it arrives in three parts: the range, the single assumption that would blow it, and the smallest thing you would cut to protect the date
  • Rewrite an objection you have actually made — the "we can't do that" version — as two options with their costs, so the choice ends up with the person who owns it

Where candidates lose points

Forking the product per client instead of building a configuration or extension point

A fork is the cheapest possible answer for one client and the most expensive possible answer for the estate. Maintenance cost grows as O(clients x changes): a single security patch now needs one port per fork, each with its own conflicts, its own test run and its own release window, and the forks drift further apart with every port. The honest rule is that a required fork is a product gap, so it should either become a supported extension point with a stable interface, or be an explicit, time-boxed and separately priced artefact that is never expected to receive upstream fixes.

Depending on a client field because it is always populated

It is populated by their operational process, not by a constraint in their database, and their process changes without telling you. When it stops being populated, a permissive pipeline writes nulls or defaults into derived data and the damage spreads for weeks before anyone notices. Validate at the boundary and fail the run with failure_class='schema_mismatch' plus a redacted sample, so the failure is loud, attributable to the client's change, and cheap to diagnose. A run that fails clearly is far cheaper than a backfill that has to reconstruct which rows were silently wrong.

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.

Optimising an axis nobody named

Ask which resource is actually scarce here: wall-clock latency, throughput, memory footprint, cost per request, or engineering time. Shaving a constant factor off an in-memory step is wasted effort when the same function makes a blocking remote call inside the loop.

Questions & practice

Resolve effective-dated engagement versions for two million access checks

Hard · binary search · effective dating · complexity budget

The engagement table holds 500,000 effective-dated rows: (engagement_id, version, effective_from, effective_to which is NULL for the open version). A database exclusion constraint guarantees that per engagement_id the tstzrange(effective_from, effective_to) values never overlap, and the ranges are half-open. You must answer 2,000,000 access checks of the form (engagement_id, at_time) in one process with no database, returning the version open at that instant or none. Also report, per engagement, any coverage gap between adjacent versions. Give the complexity of the scan-per-check approach and say why it is not an option.

Approach

  1. State the naive cost first: scanning the table per check is 2e6 x 5e5 = 1e12 range comparisons. Even at 1e9 simple comparisons per second that is roughly fifteen minutes of pure compare work, against a budget measured in seconds. It is correct, just unaffordable, and the fix is an index built once rather than a faster comparison.
  2. Build a hash map engagement_id -> array of versions sorted by effective_from, once. O(V log V) to sort, O(V) memory. The build is amortised over 2e6 lookups, which is what makes it worth doing at all.
  3. Answer each check by binary-searching for the last effective_from <= at_time, then confirming effective_to IS NULL OR at_time < effective_to. If the confirmation fails, the instant lies in a gap and the answer is none, not the neighbouring version. O(log v) per check, O(V log V + Q log v) total.
  4. Name the precondition: predecessor search is only valid because the ranges per engagement do not overlap, which the exclusion constraint enforces with btree_gist for the equality operator. Without that guarantee you need an interval tree returning all stabbed intervals, at O(log n + k). Use the same half-open convention as the database, where at_time equal to effective_to belongs to the next version.
  5. Find gaps with one O(V) pass over each sorted array comparing prev.effective_to against next.effective_from. The exclusion constraint forbids overlaps, not holes, so a hole is entirely possible and is where an access check fails closed for a reason nobody can see in the contract.
  6. If the checks can be buffered, sort them by (engagement_id, at_time) and merge against the version arrays: O(Q log Q + V log V), usually a large constant-factor win over 2e6 random binary searches because the merge walks memory in order.

Follow-up

  • The open version has effective_to NULL. How do you keep both the sort and the comparison total without a sentinel that overflows your timestamp type?
  • A gap exists for one engagement and a check lands in it. What should the access path return, and what should it emit so someone fixes the amendment?
  • Amendments are being written while you answer checks. What read model keeps an answer self-consistent, and what does a check mean if the snapshot is a second stale?

Measure credential exposure past engagement close without double counting

Medium · interval merge · sweep line · credential ttl

For one engagement you have credential_grant rows: (grant_id, principal_id, target_system_id, issued_at, not_after, revoked_at which may be NULL). The cutoff instant T is the engagement's ends_on plus access_grace_days. A grant is live over the half-open interval [issued_at, min(not_after, revoked_at)). There are up to 200,000 grants across target systems. For each target_system_id, return the total wall-clock time after T during which at least one grant was live, plus the disjoint segments that make it up. Grants that overlap must be counted once.

Approach

  1. Clip each grant to [max(issued_at, T), min(not_after, coalesce(revoked_at, +inf))) and discard any interval whose start is not strictly before its end. O(n), and it removes every grant that already expired inside the engagement window.
  2. Bucket the survivors by target_system_id and sort each bucket by start. O(n log n) overall, and sorting dominates the whole algorithm.
  3. Sweep each bucket once, holding one open segment [s,e): if the next start is greater than e, emit [s,e) and open a new segment, otherwise set e = max(e, next_end). O(n) after the sort, O(1) working state, O(number of emitted segments) output.
  4. Total exposure is the sum of emitted segment lengths, which is the measure of the union. Summing per-grant durations instead answers a different question and is unbounded above by the elapsed wall clock.
  5. Produce a second, conservative figure that ignores revoked_at and uses not_after alone. revoked_at records that you asked for revocation; whether the client's identity provider honoured it is not something this table knows, and a self-contained token validated offline stays valid to its own expiry regardless.
  6. If a bucket does not fit in memory, sort externally and sweep the stream, or push end timestamps into a min-heap keyed by end and pop those below the current start. Same O(n log n), memory proportional to the maximum number of concurrently live grants.

Follow-up

  • Three grants overlap and one of them belongs to an offboarded contractor. What must the sweep emit so exposure can be attributed per principal?
  • Which of your two numbers is the real bound on access if the client system validates tokens offline, and what does that imply about issuing TTLs in the first place?
  • Run this across 20,000 engagements as a nightly job with a fixed memory budget. What changes in the shape of the computation?

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?

Denormalise the entitlement snapshot without freezing the clock

Medium · denormalisation · read patterns · caching

Authorisation currently joins engagement (effective-dated, with status, ends_on, access_grace_days, effective_from, effective_to), entitlement and principal_assignment on every request, at the platform's full read rate, against tables taking a few hundred writes a day. You are asked to publish a denormalised snapshot that callers cache with a few seconds of staleness and invalidate on change events. Specify the snapshot's key and shape, say which columns you denormalise, name the one thing that must not be precomputed, and say what a cache miss does.

Approach

  1. Justify the shape from the ratio rather than from taste: a few hundred writes a day against the whole estate's read rate is the case for a published read model. The normalised tables stay the system of record; the snapshot is derived, versioned and disposable, and can be rebuilt from source at any time.
  2. Key it on (client_id, principal_id) with a monotonic snapshot_version and a generated_at. Consumers cache by version, refresh on a change event, and reject anything older than the staleness bound, which turns the bound into an enforced property instead of an assumption about how fast events usually arrive.
  3. Denormalise only what changes on a write: the open engagement version's engagement_id, status, isolation_tier, data_region, ends_on, access_grace_days, and the resolved scope set. Resolving the effective-dated row once at publish time — selecting the version WHERE effective_to IS NULL — is the entire saving, because that resolution is what the per-request join was paying for.
  4. Do not precompute is_active or any boolean that folds now() into the stored value. A write event invalidates the snapshot; the passage of time does not, so an engagement that ended an hour ago keeps authorising until something unrelated triggers a republish. Ship ends_on and access_grace_days as data and compare them to the current time at read, so time-based expiry needs no invalidation at all.
  5. Fail closed on a miss or on over-staleness: deny, and do not fall through to the source tables. A fallback read path means the first mass cache miss converts an availability incident into either an authorisation bypass or a stampede onto the one service whose availability target already exceeds everyone else's.
  6. State the cost you accepted: a revoked entitlement is still honoured for up to the staleness bound. That is defensible for widening access and not for narrowing it, so pair it with an out-of-band deny signal for revocation and closure, and write the residual window into the runbook instead of leaving it for an auditor to discover.

Follow-up

  • How do you detect the snapshot quietly diverging from the source tables, given that a wrong snapshot is silently permissive?
  • An amendment creates a new engagement version mid-request. Which version does the in-flight request use, and can you defend that?
  • The snapshot carries data_region. What stops the snapshot itself from being replicated somewhere its own residency rule forbids?

Soft-delete environments without breaking uniqueness or audit history

Easy · soft deletes · partial indexes · audit history

environment holds one row per provisioned deployment: environment_id, client_id, engagement_id, env_name, region, status, destroyed_at TIMESTAMPTZ NULL. Decommissioned environments stay readable for seven years, and env_name must be unique per client among live environments only, because a client may reuse a retired name. Design the constraint set and the history table, write the query that lists a client's live environments, and say what your design does about connector_run rows whose foreign key points at a destroyed environment.

Approach

  1. Keep the tombstone as a nullable timestamp rather than a boolean: destroyed_at carries when as well as whether, and enforce live-only uniqueness with a partial index, CREATE UNIQUE INDEX env_live_name ON environment (client_id, env_name) WHERE destroyed_at IS NULL, which indexes only the live subset and so stays small.
  2. Get the NULL semantics right before you pick a spelling. In a B-tree unique index two NULLs compare distinct by default, so plain UNIQUE (client_id, env_name, destroyed_at) enforces nothing at all among live rows — every live row is unique on the third column purely by virtue of being NULL. PostgreSQL 15's UNIQUE NULLS NOT DISTINCT (client_id, env_name, destroyed_at) does satisfy the requirement: the NULL live rows now compare equal and collide with each other, while retired rows carry distinct destroyed_at timestamps and stay distinct, so a name can be retired and reused any number of times.
  3. Prefer the partial index anyway, and be able to say why rather than asserting it. It indexes only the live subset — tens of rows per client — instead of seven years of tombstones; it needs no version floor; and it keeps destroyed_at out of the key, so it cannot be defeated by two rows retired at the same instant. That last case is reachable: now() is transaction-start time, so retiring a name, recreating it and retiring it again inside one transaction writes two identical destroyed_at values and raises a duplicate-key error under the constraint form. clock_timestamp() avoids it if you take that route.
  4. Put history in a separate append-only table written by an AFTER INSERT OR UPDATE OR DELETE trigger holding environment_id, changed_at, changed_by, op and the before and after row images. PostgreSQL has no system-versioned tables, so this is a trigger or a logical-decoding consumer, not a DDL flag, and the soft delete arrives at that trigger as an UPDATE rather than a DELETE.
  5. State the foreign-key consequence rather than leaving it implicit: a soft delete leaves every referencing connector_run row resolvable, which is exactly what the audit requirement wants, but it means no read is protected by the database. Give the predicate one home, a view or an RLS-backed view that every caller uses, instead of repeating destroyed_at IS NULL across hundreds of call sites.
  6. Size the retention: seven years of history plus tombstoned rows in the hot table. Partition the history table by month so expiry is a DETACH PARTITION rather than a DELETE that has to be vacuumed, and decide separately whether a residency-driven erasure request can be honoured against an append-only store at all.

Worked solution (20 min)

  1. Write the DDL with destroyed_at TIMESTAMPTZ NULL plus the partial unique index on (client_id, env_name) WHERE destroyed_at IS NULL.
  2. Insert two live rows sharing (client_id, env_name) and confirm the second is rejected; set destroyed_at on the first and confirm the insert now succeeds.
  3. Build the same table a second time under UNIQUE NULLS NOT DISTINCT (client_id, env_name, destroyed_at) and confirm it passes the same two tests, so you are choosing between two working designs rather than rejecting a broken one.
  4. Add environment_history (history_id, environment_id, changed_at, changed_by, op, row_before JSONB, row_after JSONB) and the AFTER trigger that populates it.
  5. Write the live-listing query: SELECT environment_id, env_name, region, status FROM environment WHERE client_id = $1 AND destroyed_at IS NULL ORDER BY env_name.

Expected result: A partial unique index covering only live rows, a separate append-only history table with its own partitioned retention, and a listing query whose predicate matches the index predicate exactly so the planner can use it.

Verify it yourself

  • Run retire-and-recreate three times for the same name: three tombstoned rows and one live row coexist without violating the index.
  • On the NULLS NOT DISTINCT variant, retire and recreate the same name twice inside one transaction and watch it fail on duplicate destroyed_at, since now() does not advance within a transaction; the partial index has no such failure mode.
  • EXPLAIN the listing query and see the partial index; widen the predicate to destroyed_at IS NULL OR destroyed_at > now() and watch the index drop out, because that predicate no longer implies the index's.
  • Soft-delete a row and confirm the history trigger fired; a DELETE-only trigger records nothing, which is the common way this audit trail ends up empty.

Follow-up

  • What stops the next endpoint from forgetting the destroyed_at IS NULL predicate, and does that mechanism survive a developer writing raw SQL?
  • The history table stores row images as JSONB and the base table gains a column. What do old rows now mean, and how do you read them six years later?
  • A residency erasure request requires a genuine hard delete for one client. What breaks, and what do you keep?

Explain the core differences between HTML5, CSS3, and modern JavaScript.

Medium

Approach

  1. Work from the requirement backwards to the design.
  2. State your assumptions explicitly before working the problem.
  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 code remains maintainable and clean?

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  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?

Design the error contract for engagement-scoped credential minting

Easy · error-taxonomy · credentials · fail-closed · http-semantics

The Access Broker mints short-lived credentials for client-owned systems. Its callers are the connector runtime, which is unattended and retries automatically, and an operator CLI with a person waiting. A request names an engagement, a target system, a set of scopes and a requested TTL. The broker refuses past the engagement's end date plus its recorded grace, caps any requested TTL at the remaining window, and fails closed when its own dependencies are unavailable. Design the response contract: the success shape, the failure classes each caller must tell apart, the status codes that carry them, and the required action per class.

Approach

  1. Fix the success shape first, because the interesting case hides there. Return grant_id, the granted not_after, the scopes actually granted and the correlation id. Capping is not an error: a request for twelve hours two hours before the engagement ends is a 201 whose not_after is two hours out. Make it contractual that the caller schedules its next mint from the returned not_after and never from the value it asked for, or it will keep using a credential the broker already considers dead and discover it as a mid-run auth failure.
  2. Classify refusals by what the caller does next, not by where they came from: terminal for good (engagement closed, terminated, or past end plus grace), terminal until something changes (scope not entitled, unknown target system), caller-fix (malformed scopes or an unparseable TTL), and retry-later (rate limited, or the broker failing closed on its own dependency). Map them to 403, 404, 422, 429 with Retry-After and 503 with Retry-After, each carrying a distinct stable code the connector switches on.
  3. Keep fail-closed on the 5xx side deliberately. If a dependency outage returns 403, an outage is indistinguishable from an offboarding, and the unattended connector concludes a live engagement is over and stops scheduling it. The availability requirement stated for this service is exactly why the distinction has to be visible in the status code rather than only in a message.
  4. Separate 'engagement closed' from 'engagement suspended' with different codes even though both are 403. The first means stop scheduling this binding permanently and raise it to a human; the second may succeed after reinstatement, so the correct behaviour is a long backoff. One code covering both forces the connector to guess, and it will guess the expensive way.
  5. Serve both callers from one body: a stable machine-readable code plus a human message. The CLI renders the message; the connector switches on the code and records failure_class ('auth_expired' versus 'our_bug'), so failures stay attributable to an owner. Write an issuance or refusal record on every path, including refusals, and echo issued_by_request_id in the response, so a later question about who held access at a given minute has one identifier crossing both systems.

Worked solution (20 min)

  1. Write the success body's fields and, beside each, which caller reads it and what it does with it; delete any field neither caller acts on.
  2. List every reason the broker can refuse, then write the required action for the unattended connector and for the operator separately. Wherever the two differ, that is a separate stable code.
  3. Assign status codes, and check that no reason lands on a code whose generic client behaviour contradicts the action you require.
  4. Write the connector's handling as a switch on the stable code and confirm only the rate-limited and transient classes retry.

Expected result: A 201 carrying grant_id, the granted not_after, granted scopes and the correlation id; a refusal table in which engagement-closed, engagement-suspended, scope-not-entitled, unknown-target, malformed-request, rate-limited and broker-unavailable each have a distinct stable code; retries only on 429 and 503; and an audit record written on every path including refusals.

Verify it yourself

  • A TTL longer than the remaining engagement window succeeds with a shortened not_after rather than failing, and the caller's next mint is scheduled from the returned value.
  • Taking the broker's dependency down yields a retryable 5xx, and nothing in the connector's handling reads that as the engagement being over.
  • Every refusal leaves a record an auditor can find by correlation id, and the same id appears in the response the caller logged.

Follow-up

  • The broker returns 503 for ninety seconds during a dependency failure. What does the unattended connector do, and how do you keep that from polluting the per-binding failure rate you alert on?
  • A caller requests a twelve-hour TTL two hours before the engagement ends. What do you return, and what specifically breaks if you return an error instead?

Environment list page slows as the estate grows

Easy · n+1 query · latency · batching

An estate console lists environments for an operator: environment_id, client name, isolation_tier, the desired release version string, the reported release version string, and the engagement's current status. A 10-row filter returns in 25 ms; the unfiltered 200-row page takes 640 ms. No single statement exceeds 0.5 ms, and the database records 553 statements for that request. Rows where reported_release_id is NULL are measurably cheaper than the rest. Diagnose the cause and give the fix, stating the statement count you expect afterwards.

Approach

  1. Get the shape before theorising about plans. Divide the per-request statement delta in pg_stat_statements by the rows returned: 553 statements for 200 rows, none over 0.5 ms, is round trips rather than work, and rules out a bad plan immediately.
  2. Account for 553 exactly, because the arithmetic is the diagnosis and not a detail. One driver query, one desired-release lookup per row, one current-engagement lookup per row, and one reported-release lookup only for the rows where reported_release_id is non-null: 1 + 200 + 200 + 152. The 152 is why client-managed rows are cheaper, and if the sum did not land on the observed number something else is firing too.
  3. Sanity-check the cost model: 553 round trips at roughly 1 ms each is about 0.55 s on top of a 25 ms baseline, which accounts for the 640 ms. A hypothesis that does not reproduce the measured number is not finished.
  4. Batch it. Collect the ids from the driver query, then fetch releases with WHERE release_id = ANY($1) and the open engagement versions with WHERE engagement_id = ANY($1) AND effective_to IS NULL, and assemble in memory. Three statements per page, independent of page size.
  5. Check index usage before declaring victory. The engagement fetch must use the partial unique index on (engagement_id) WHERE effective_to IS NULL; without it, ANY($1) scans every amendment version of every engagement and the fix regresses harder than the bug did.
  6. Pin the behaviour with a test asserting a constant statement count per request, since no individual query here is slow enough to ever trip a slow-query alarm.

Follow-up

  • The operator wants to sort the page by reported release version. Where does that sort have to happen now, and what does it cost?
  • Page size goes to 1,000. Which breaks first, the driver query or the ANY($1) parameter list?
  • reported_release_id NULL means unknown, not converged. How should the list render that, and what does the count at the top of the page then mean?

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.

Review and mentoring: what you let through

Counting review comments or mentees proves nothing. The useful version is a specific change you approved with a reservation you stated, or one you blocked and the delay that cost. Say which standard you were holding and why it was worth the friction. A mentoring story needs the thing the other person can now do without you.

Can you describe a time you had to troubleshoot a complex issue under pressure?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. Pick a story where you made the decision, not one where you watched it.
  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?

How do you communicate technical constraints to non-technical stakeholders?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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

Walk me through your most significant past project and your specific contribution to it.

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Give the blast radius: what could have broken, and what you measured.
  3. State the situation in two sentences and spend the rest on the reasoning.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?

How do you handle tight deadlines when developing new features?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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