cswork / libraryOpen practice workspace

Interview guides · Aplin & Martin Consultants

Aplin & Martin Consultants Software Engineer Interview Questions & Guide 2026

Aplin & Martin Consultants software engineer prep on operations: decide what you would need to see to know a release is going wrong.

The 60-second brief

At Aplin & Martin Consultants, the Software Engineer role bridges the gap between traditional civil engineering workflows and modern digital solutions. You will develop and optimize the technical tools that support the company's infrastructure, planning, and design teams in delivering projects across Canada. Your work directly influences the efficiency and precision of complex urban and infrastructure developments.

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

Focus areas

  • Evolve schemas expand-contract across un-upgradable client deployments
  • Bound credential lifetime by the engagement end date
  • Scope every query by tenant below application code

Overview

This role is unique because it requires a hybrid mindset: you must be a capable developer who understands the nuances of civil design software. You will not just be writing code; you will be solving real-world spatial, structural, and logistical challenges. Success in this position requires a passion for integrating technical automation into the physical world.

Where to focus your preparation

Treat the sections below as preparation areas and confirm the format with your recruiter.

Preparation focus

No round sequence has been reported for this company, so work the categories below and confirm the format with your recruiter.

What to demonstrate

  • Breadth across SQL, experimentation and product reasoning
  • Ability to state assumptions before choosing a method

How to prepare

  • Drill the practice exercises below and time yourself
  • Prepare three quantified stories about decisions you drove

Where candidates lose points

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.

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.

Comparing floating-point values for equality, or holding money in them

Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.

Trusting input because it came from your own front end

Anything crossing a trust boundary is hostile: parameterise queries instead of building SQL by concatenation, validate against an allow-list rather than a deny-list, and bound the size of anything you allocate from a request. Raising this unprompted in an API or design question is a cheap and unusually strong signal.

Questions & practice

Find peak window utilisation and the first rejected request

Medium · sliding window · two pointers · rate limiting

One connector binding's requests against a client endpoint arrive as timestamps t[0..n-1] in milliseconds, sorted non-decreasing, n up to 1,000,000. The endpoint's budget is R requests per rolling window of W milliseconds, and a request at time t is admitted only if strictly fewer than R already-admitted requests fall in the half-open window (t-W, t]. Return (a) the maximum number of requests falling in any W-millisecond window, counting every request regardless of admission, and (b) the index of the first request the limiter would reject. Both parts in O(n) time.

Approach

  1. Part (a) is a two-pointer scan: for each right index, advance left while t[right] - t[left] >= W, which leaves the window exactly (t[right]-W, t[right]], and track max(right-left+1). Left is monotone non-decreasing, so total work is O(n) amortised with O(1) extra space. The maximum over all windows of width W is attained at a window whose right edge is a request timestamp, so scanning right edges is sufficient.
  2. Part (b) is not the same scan. A rejected request consumes no budget, so the window must count admitted requests only. Keep a FIFO deque of admitted timestamps: pop from the front while front <= t - W, admit if the deque holds fewer than R, and return the first index where it does not. O(n) total pushes and pops, O(R) memory.
  3. Pin the boundary convention before writing either loop. With a half-open (t-W, t] window a request exactly W milliseconds after an earlier one does not see it; switching to a closed window changes the answer at every boundary and is where most disagreements with the client's own limiter come from.
  4. Note what fixed buckets of width W buy and cost: O(1) memory, but they admit up to 2R inside a span shorter than W, R at the end of one bucket and R at the start of the next. If buckets are unavoidable, halve the bucket width or use a weighted estimate across two adjacent buckets.
  5. If part (a) already exceeds R, the binding cannot run at this concurrency under any limiter shape. That is a scheduling problem, not a retry-policy problem, and no backoff tuning fixes it.

Follow-up

  • Three bindings and four workers share this one endpoint's quota. Where does the counter live, and what is the correct behaviour when that store is unreachable?
  • Add full-jitter backoff to the retry path and state precisely what it prevents that plain exponential backoff does not.
  • The client documents the limit as 'about 100 per minute'. How would you measure the real one without tripping it in their production?

Collapse connector retry attempts into logical jobs and failure counts

Easy · hash aggregation · idempotency · retries

You are given one UTC day of connector_run rows: (connector_id, idempotency_key, attempt_no, status, failure_class, records_read, records_applied), unique on (connector_id, idempotency_key, attempt_no), up to 5,000,000 rows in arbitrary order. Retries of one logical job share the idempotency_key; the job's outcome is the status of its highest attempt_no. Return, per connector_id: the number of logical jobs, a count of terminally failed jobs by failure_class, and the dedupe hit total, meaning sum(records_read - records_applied) over terminal-succeeded attempts only. One pass over the input; state your memory bound.

Approach

  1. Key a hash map on (connector_id, idempotency_key) and keep only the highest attempt_no seen so far plus that row's status, failure_class and record counts. One pass, O(n) time, O(d) space where d is the number of distinct logical jobs, not O(n).
  2. Treat a repeated (key, attempt_no) as corrupt input and raise, since the table's uniqueness constraint says it cannot happen; silently overwriting hides a double-insert in the runtime.
  3. Fold the d surviving entries into per-connector counters in a second pass over the map, not over the rows. The failure_class histogram is a small fixed-width map per connector because failure_class is an enum.
  4. Compute dedupe hits only from terminal-succeeded attempts. A failed attempt's records_read is work attempted, not work deduplicated, and adding it counts the same source records once per retry.
  5. If d does not fit in memory, partition the input by hash(connector_id, idempotency_key) into p files and aggregate each partition independently. Same O(n) total work, p sequential passes, memory traded for I/O; the partition function must use the full key or a job's attempts split across files.

Worked solution (20 min)

  1. Take six rows: (c=7,K=A,1,failed,client_429,0,0), (c=7,K=A,2,succeeded,null,500,480), (c=7,K=B,1,ambiguous,client_timeout,300,300), (c=7,K=B,2,succeeded,null,300,0), (c=9,K=C,1,failed,schema_mismatch,0,0), (c=9,K=C,2,failed,schema_mismatch,0,0).
  2. Build the map: A -> attempt 2 succeeded, B -> attempt 2 succeeded, C -> attempt 2 failed/schema_mismatch. Three entries from six rows.
  3. Fold per connector: connector 7 has 2 logical jobs, 0 terminal failures; connector 9 has 1 logical job, 1 terminal failure classed schema_mismatch.
  4. Dedupe hits from terminal-succeeded attempts only: (500-480) + (300-0) = 320 for connector 7, 0 for connector 9.
  5. Compute the wrong answer deliberately by counting attempt rows: 6 jobs and 3 failures, which is what a naive GROUP BY on run rows returns.

Expected result: connector 7: 2 logical jobs, no terminal failures, 320 dedupe hits. connector 9: 1 logical job, 1 terminal failure with failure_class='schema_mismatch', 0 dedupe hits. Attempt-level counting would have reported 6 jobs and 3 failures.

Verify it yourself

  • The sum of terminal statuses across all connectors equals the number of distinct (connector_id, idempotency_key) pairs, here 3.
  • records_applied <= records_read holds on every row, so the dedupe total is non-negative.
  • Rerunning the aggregation over a shuffled copy of the input gives identical output, since the highest attempt_no wins regardless of arrival order.
  • Map size equals 3, strictly less than the 6 input rows, confirming collapse actually happened.

Follow-up

  • A job's highest attempt is 'cancelled' but an earlier attempt succeeded. What is the job's outcome, and what does that say about who writes the cancel?
  • Produce this incrementally as runs land instead of as a daily batch: what state do you keep per key, and what happens when an attempt arrives out of order?
  • records_applied is written by your runtime, not by the client. What would you reconcile it against before anyone trusts the dedupe-hit number?

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?

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.

Worked solution (25 min)

  1. Write the snapshot record's field list and key, marking each field as write-invalidated or time-evaluated.
  2. Write the publisher query that resolves the open engagement version and aggregates scopes per (client_id, principal_id).
  3. Write the consumer's decision function, taking the snapshot plus a current timestamp, and show that it denies on missing, on stale, and on ends_on plus grace elapsed.
  4. Write the reconciliation query that recomputes a sample of keys from source and diffs them against the published snapshot.

Expected result: A (client_id, principal_id)-keyed record carrying resolved scopes plus engagement_id, status, isolation_tier, data_region, ends_on, access_grace_days, snapshot_version and generated_at; every time-dependent decision evaluated at read; missing or over-stale entries denied rather than backfilled from source.

Verify it yourself

  • Freeze the publisher and advance the clock past ends_on plus grace: authorisation must start failing with no new event and no republish.
  • Delete a snapshot entry and confirm the caller denies rather than querying the source tables.
  • Reconciliation over a sample of keys reports zero differences outside the staleness bound; any difference inside it is explainable by a specific event.

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.

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?

How has your educational background prepared you for a role at the intersection of software and civil engineering?

Medium

Approach

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

What are your past work experiences?

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. Say what you would check first and why it is the highest-information step.
  3. Work from the requirement backwards to the design.

Follow-up

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

Can you describe a project where you successfully implemented a technical solution to improve a design workflow?

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

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

Place tenants across pooled and dedicated stores without leaking rows

Hard · tenant isolation · sharding · connection pooling · blast radius

Every row that is not global reference data carries client_id, and a query run for one client must never read another's. The platform serves shared-pool clients from one PostgreSQL cluster behind a transaction-pooling proxy, dedicated clients from a database each, and every region separately. Growth takes the estate from 200 clients to 2,000. Design the placement and the enforcement: the partition key, which layer enforces isolation, what that layer still lets through, and what breaks first when the client count multiplies by ten.

Approach

  1. Fix client_id as the partition key and place by (region, isolation_tier). Residency makes region a hard partition with no cross-region query at all, and the tier selects the store. Resist engagement_id as the key even though it is the authorisation root: engagements start and end inside one client, so a key that changes under a row turns a contract renewal into a data migration.
  2. Enforce below the application with row-level security keyed on a transaction-scoped setting: SET LOCAL app.client_id at the start of each transaction, policies of the form USING (client_id = current_setting('app.client_id', true)::bigint). Three specifics decide whether it holds. A plain SET survives the connection's return to a transaction-pooling proxy, which does not reset session state between transactions by default, so a handler that forgets its SET inherits the previous tenant's value and the policy then enforces the wrong client flawlessly. Policies do not apply to the table owner unless FORCE ROW LEVEL SECURITY is set, and never apply to a role with BYPASSRLS, so the migration role and the serving role must be different roles.
  3. State what RLS still lets through, because a boundary you cannot enumerate is not a boundary: it scopes rows, not an aggregate you computed earlier, not a cache entry keyed without the client, not a log line or an error payload carrying another client's data, and not a bug in the code that sets the variable. Assert that the setting matches the request's client immediately before the transaction's first query rather than trusting the middleware, which converts a leak into an error for the price of one comparison.
  4. Cost the alternative at both sizes rather than in principle. Database-per-client is comfortable at 200. At 2,000 the binding constraints are connection count (a pool per database per service instance, against PostgreSQL backends that are processes costing megabytes each), migration fan-out (one schema change becomes 2,000 runs whose slowest or half-failed member defines your release), and backup and restore surface. Pooled-with-RLS inverts every one of those costs and pays for it with a blast radius of one cluster.
  5. Choose per tier rather than globally: pooled with RLS as the default, a dedicated database where the contract demands it, and identical application code in both because the code never selects its own scope. What remains is routing, and routing belongs at connection acquisition, keyed by the client on the request, so a mis-routed request fails to acquire rather than succeeding against the wrong store.

Worked solution (40 min)

  1. Create two tables carrying client_id, enable ROW LEVEL SECURITY and FORCE ROW LEVEL SECURITY, and add a policy reading current_setting('app.client_id', true).
  2. Put a transaction-pooling proxy in front and interleave requests for two clients over a small pool, with one code path using SET, one using SET LOCAL, and one deliberately omitting it.
  3. Run the same queries as the table owner and as a role holding BYPASSRLS.
  4. Leave the variable unset on a fresh backend and run a plain SELECT.

Expected result: The path that omits its SET returns the previous checkout's client rows whenever the session variable was left behind by a plain SET, and nothing errors or logs, because the policy is enforced correctly against the wrong value. The same omission under SET LOCAL leaves the variable unset. With it unset, current_setting(..., true) is NULL, the comparison evaluates to NULL rather than true, and the query returns zero rows - a visible failure instead of a leak. The owner sees everything without FORCE, and the BYPASSRLS role sees everything regardless.

Verify it yourself

  • Every table holding client data has both ROW LEVEL SECURITY and FORCE ROW LEVEL SECURITY enabled, verified by a test over the catalog rather than by review.
  • The serving role owns no table and does not hold BYPASSRLS.
  • A transaction reaching its first query without app.client_id set returns nothing or raises; it never returns rows.

Follow-up

  • Show me a support tool that legitimately reads across clients. What makes it safe, and how is each of its reads attributed?
  • One pooled client's query plan regresses and saturates the cluster. Who else notices, and what did you build in advance to contain it?
  • A row turns up under the wrong client. What is your first query, and what is your notification obligation while you run it?

Entitlement snapshot expires in lockstep across the fleet

Medium · cache stampede · ttl jitter · single-flight

Delivery requests across the estate return 403 for roughly 200 ms every five seconds. The entitlement service publishes one versioned snapshot; every caller caches it for five seconds and fails closed on a miss. Its request rate is near zero for most of each window and about 4,000 in a 300 ms burst, spread across the 800 application processes a rolling deploy restarted this morning. Its own handler service time is unchanged. Diagnose the shape, then give a fix that preserves fail-closed behaviour.

Approach

  1. Establish the period before the cause. Plot the origin's request rate at one-second resolution: a sawtooth whose period equals the cache TTL, with the mass inside a few hundred milliseconds, says the misses are synchronised, not that traffic grew.
  2. Measure two things next, in this order: key cardinality and in-flight concurrency per process. One key across the whole fleet means every process misses at the same instant. Four thousand requests from 800 processes means roughly five concurrent misses inside each process, so there is no single-flight and each in-flight request for the same key issues its own fetch.
  3. Explain why the phases are locked: the rolling deploy populated every cache within the same few seconds, so 800 independent five-second TTLs expire together and nothing in the system ever scatters them again.
  4. Fix three independent defects rather than one. Single-flight per key per process, so a process has at most one outstanding fetch for a key. A per-process randomised TTL, for example uniform over four to six seconds, so expiry decorrelates after any synchronising event. Refresh-ahead at about 80 percent of the TTL, so the hot key is replaced before it expires and a request never waits on a miss at all.
  5. Keep fail-closed honest about what staleness costs. Serving a stale snapshot on a fetch error is safe for the engagement end-date case, because ends_on travels inside the snapshot and the caller evaluates it locally. It is not safe for a mid-engagement suspension, which only arrives as an invalidation event. So bound the stale window explicitly at tens of seconds, fail closed beyond it, and state the resulting suspension propagation delay as the accepted price.
  6. Name the new steady state so nobody is surprised by it: compute it from the refresh interval rather than the TTL, because refresh-ahead is what now sets the period. Refreshing at 80 percent of a 5-second TTL means each process fetches every 4 seconds, assuming the refreshed entry restarts the TTL clock, so the floor is fleet size divided by the refresh interval: 800 / 4 = 200 requests per second, not the 800 / 5 = 160 that the TTL alone would suggest. Jitter nudges it up rather than down, because the rate averages 1/T and not 1/mean(T): with TTL uniform over 4 to 6 seconds the floor is about 203 requests per second. A process that stops asking for the key stops refreshing it, so 200 is the floor for a uniformly hot fleet and an upper bound for any other traffic mix. Verify by replaying 800 synchronised clients and confirming the origin sees a flat rate at that level with no burst above a stated ceiling.

Follow-up

  • At 8,000 processes the steady refresh floor becomes 8,000 / 4 = 2,000 requests per second, set by process count and refresh interval alone and independent of user traffic. What changes in the design?
  • A client is suspended for non-payment at 09:00. Walk the worst-case time until every process stops serving them, with your stale window in place.
  • How would you detect the next stampede automatically, given that the origin's average rate barely moves when one occurs?

Suggested practice checklist

Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.

Day 1 — Fix the scope and take a cold baseline

  • Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
  • Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
  • Set the week's rule: one thing finished every night, including the night you only have forty minutes.

Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.

Day 2 — One pattern, written three times from blank

  • Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
  • On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
  • Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.

Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.

Day 3 — One design, only to the depth you can defend

  • Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
  • Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
  • Write the one tradeoff you are choosing against and the observation that would make you reverse it.

Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.

Day 4 — Only the fundamentals you will have to defend

  • Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
  • Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
  • Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.

Deliverable: Three written answers, each under two hundred words and each timed aloud.

Day 5 — Your own work, timed

  • Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
  • Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
  • Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.

Deliverable: Two timed narratives with one defensible number in the opening line.

Day 6 — The one full rehearsal, in the weekend block

  • Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
  • Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
  • Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.

Deliverable: Mock notes naming three failure moments with a specific fix written under each.

Day 7 — Taper

  • Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
  • Re-read only your own notes from this week and open no new material.
  • Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.

Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.

The failure that was yours

Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.

What is your experience with design automation?

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. Close with what you would do differently, concretely.

Follow-up

  • How did you know your change caused the improvement?
  • What did you decide not to do, and why?

Own a cross-tenant read that reached a client

Hard · tenant isolation · incident response · blast radius

Prepare a five-minute account of an isolation or data-exposure incident you owned: a query that returned another tenant's rows, a cache keyed without a tenant, or a report that crossed a boundary. State the mechanism precisely, how you established blast radius (which tenants read which tenants' rows, over what window), the containment step, and the fix that made the class impossible rather than the instance. Finish with the notification decision and who made it. If you have never owned one, use the closest near-miss and say so.

Approach

  1. Open with the mechanism in one sentence rather than the symptom. The canonical version here: a session-scoped SET app.tenant_id on a connection returned to a transaction-mode pool with that value still attached, so the next checkout inherited it and row-level security then enforced the previous tenant's policy flawlessly.
  2. Separate containment from fix. Containment is what you did in the first twenty minutes (drain the pool, switch the pool to session mode, disable the endpoint); the fix is structural (SET LOCAL, which dies with the transaction, plus an assertion that the setting equals the request's tenant immediately before the first statement).
  3. Give the blast-radius method, not an adjective: reconstruct from the query log joined to request context on a correlation id, count distinct (reading tenant, row tenant) pairs and rows, and state what you could not reconstruct and why.
  4. Name the class-level fix and its cost. FORCE ROW LEVEL SECURITY so the table owner is not exempt, separate migration and application roles because a role with BYPASSRLS defeats every policy, and a test that drives two tenants' requests concurrently over one pooled connection, which is the load profile a serial integration suite never produces.
  5. Close with the notification call: it is a contractual question, not only an engineering one, so say who decided, how long the decision took, and what you would not repeat.

Follow-up

  • Your suite ran one request at a time and passed. What test would have caught this, and what does it cost to run on every change?
  • The same defect inside a dedicated deployment touches one client. Does that change your severity, your containment, or only your disclosure?
  • How do you know today that no other endpoint in the estate has the same defect?

Ship a connector on a deadline with named debt

Medium · delivery pressure · risk bounding · idempotency

Describe shipping something you knew was incomplete against a fixed external date: a client change window, a month-end, a contractual go-live. Name the specific shortcut, such as an ambiguous outcome classified as a plain failure, a watermark advanced outside the ledger transaction, or an unchunked backfill. State the bound you put around it, who accepted the risk and where that was recorded, what would have made you refuse to ship, and what actually happened to the debt afterwards. Do not describe debt you never wrote down.

Approach

  1. Name the shortcut precisely enough that its failure mode follows from the description: no ambiguous state, so a timeout retried and duplicated; or the watermark committed in a different transaction from the ledger row, so a crash between them loses or replays a range.
  2. Give the bound, which is what separates a judged risk from a gamble: a first run restricted to a reversible slice, a record cap, a kill switch, or a reconciliation read against the target scheduled before the next run.
  3. Say who accepted the risk and in what artefact. 'The client knew' is credible only with a written record and a date.
  4. State your refusal line and why this fell on the shipping side of it. Irreversibility is the usual test: damage you can undo ships, silent corruption inside a client's system does not.
  5. Report what happened to the debt with a date, including the case where it is still live, and what it has cost since.

Follow-up

  • The shortcut fires in production. Walk through the first hour: what you look at, in what order, and what you tell the client.
  • Same deadline, but the shortcut risks writing duplicates into the client's system rather than into yours. Does your answer change?