cswork / libraryOpen practice workspace

Interview guides · 84 Lumber

84 Lumber Software Engineer Interview Guide

84 Lumber Software Engineer interview guide: the five reported rounds, behavioral and SQL tuning questions, and a 7-day prep plan.

The 60-second brief

84 Lumber is a privately held supplier of building materials with hundreds of locations, and its main customers are professional builders, referred to as Pros. Software engineers there build and maintain the company's own internal systems rather than a product sold to outside users: inventory management, supply chain and delivery logistics, an internal ERP and the point-of-sale software used in stores. The people using that software are store associates, store managers and back-office departments such as Logistics and Finance. Engineers work with product managers and business analysts across the whole lifecycle, from gathering requirements to deployment and supporting production issues that affect store operations or delivery schedules. The requirements candidates describe centre on C#/.NET or Java, SQL Server, database design, RESTful APIs, a modern front-end framework and Git, with Azure or AWS, Agile/Scrum and retail or logistics experience listed as nice to have.

This guide is for backend and full-stack engineers interviewing for a Software Engineer role at 84 Lumber; the requirements candidates describe list 3-5 years of experience for mid-level openings. It covers each of the five reported rounds, the behavioral and technical-experience questions candidates recall, how to answer the practical scenarios (tuning a slow query, an inventory system lagging at peak hours, keeping code quality high before release), and how to pitch the same project to an HR partner, a department head and a senior developer.

Candidates report five rounds over roughly 4-6 weeks: Initial Screening, Face-to-Face Interviews, High-Level Management Discussion, Technical Evaluation and Final Assessment. They describe the in-person stages as meetings with HR, potential managers, department heads and senior leadership, often at the headquarters in Eighty Four, PA.

Focus areas

  • Interview process design (multi-round screening)
  • Behavioral interviewing
  • Communication skills (verbal)

Overview

Candidates describe a loop that is conversational more than algorithmic. Most reported questions are behavioral or ask you to walk through past work, and automated coding assessments are not the centre of the process; some logic-style questions do come up. How technical the loop gets is reported to vary by team, and rounds with senior developers can go deep into coding practice and architecture. Prepare two things in parallel: project stories you can defend at any depth, and enough fluency in SQL Server and a C# or Java service to answer technical follow-ups on the spot.

The technical prompts candidates recall are practical rather than puzzle-like: improving a slow-running database query, making sure code is sound before it reaches production, a trade-off made to hit a business deadline, and a store manager reporting that the inventory system lags at peak hours. These topics are good ground to rehearse. Practise reading an execution plan, explaining locking and blocking when many registers update the same stock rows, separating reporting load from transactional load, and building a new interface on top of existing business logic without breaking stock or financial figures.

Many of the people you meet are not engineers: candidates list HR, hiring managers, department heads and senior leadership among their interviewers. Every technical story therefore needs a plain-language version that leads with what changed for a store, a delivery or a report. Candidates also report questions about long-term career goals, an emphasis on promoting from within, a facility or store tour on some tracks, and a process that can feel disorganised at times. Have a specific career answer ready, use any tour to ask how the software supports the yard and deliveries, and keep your own notes on who you met and what is still open.

The 84 Lumber interview process

The five stages below are as candidates describe them. Reported questions are not tied to a particular stage, so have every story and technical answer ready for any round.

Initial Screening

Candidates describe a first conversation covering whether your background meets the basics. Treat it as the place to establish three things quickly: that your experience matches the stack candidates describe (C#/.NET or Java, SQL Server, REST APIs, Git), why you want to build internal systems for a building-materials business, and the practical facts of the process. Ask about the in-person stages, travel to Eighty Four, PA if you are not local, the current remote or hybrid policy, and what the Technical Evaluation covers for this team, because candidates report that rigor varies.

What to demonstrate

  • Basic qualifications against the listed requirements: a backend language, SQL, version control
  • Motivation for working on software for a building-materials supplier
  • Clear, concise verbal communication

How to prepare

  • Write a specific answer to "Why are you interested in working for a building materials company?" that names inventory, point-of-sale or delivery systems and the professional builders they serve
  • Map one concrete project to each listed skill: C# or Java service, SQL Server schema or query work, REST API, Git workflow
  • List your questions for the recruiter: remote policy, travel logistics, team stack, and the format of the technical stage

Face-to-Face Interviews

Candidates report meeting several stakeholders in this stage, including HR, potential managers and department heads, often back to back and in person. The same project may need to be told several times at different depths, so prepare each story in two versions: a business version (what changed for stores, deliveries or reports) and an engineering version (your design, your code, the trade-off). Keep energy for the full sequence and log what each person cared about so later answers stay consistent.

What to demonstrate

  • Behavioral examples on teamwork, critical feedback and learning under deadline pressure
  • Explaining technical work to non-technical stakeholders such as department heads
  • Professionalism and communication in face-to-face meetings

How to prepare

  • Write STAR stories for the reported prompts on a difficult team member, critical feedback and learning a new technology quickly to meet a deadline
  • Prepare a business-level and an engineering-level version of your most complex project, each with your specific contribution
  • Prepare one question per audience: team structure for a manager, how stores use the systems for a department head

High-Level Management Discussion

Candidates describe this as a discussion with senior leadership about alignment with company values. They report a culture they describe as built on hard work, loyalty and teamwork, an emphasis on internal promotion, and questions about long-term career goals. Candidates describe it as more about fit and career goals than technical detail. Answer with a concrete career path inside an internal-systems team and evidence from your history, such as a promotion or long tenure, and ask leadership how technology is meant to support stores and Pros over the next few years.

What to demonstrate

  • Alignment with the values candidates describe: work ethic, loyalty, team orientation
  • Long-term career goals and interest in growing inside the company
  • Understanding of how software serves the business

How to prepare

  • Write a specific answer on where you want to be in a few years, tied to internal systems work rather than a generic title
  • Pick one example of growth or tenure at a previous employer and the responsibility it led to
  • Prepare two business questions for leadership about the systems roadmap and how stores and deliveries use the software

Technical Evaluation

Candidates report technical discussions and problem-solving scenarios relevant to the role, with rigor that varies by team; when senior developers lead, the discussion can go into coding practices and architecture. The reported technical prompts are practical: tuning a slow query, how you ensure quality before production, a trade-off made for a deadline, and an inventory system lagging at peak hours. Candidates also report that problem-solving discussions turn to edge cases in supply chain or retail settings. Answer by clarifying the situation, stating how you would measure before changing anything, and naming the data-integrity risk of each fix.

What to demonstrate

  • Technical discussions and problem-solving scenarios relevant to the role
  • Structured problem solving on retail, inventory or supply chain scenarios
  • Coding practices and architecture knowledge where senior developers are involved

How to prepare

  • Rehearse a slow-query walkthrough on SQL Server: actual execution plan, logical reads, sargability, statistics, parameter sniffing, covering indexes and their write cost
  • Outline a diagnosis for the inventory system lagging at peak hours: blocking on stock rows, reports competing with transactions, N+1 calls, and fixes that keep stock counts correct
  • Write out your quality practice before release: unit and integration tests, code review, CI checks, staged rollout and rollback
  • Refresh REST API design and a small C# or Java service with data access, so you can sketch code if asked

Final Assessment

Candidates describe a closing conversation about whether you and the team suit each other, including team culture. Use this stage to close gaps: anything you answered weakly earlier, anything you still do not know about the team, and the practical terms you have not confirmed. Bring a short list of open questions from your notes on earlier rounds and one example that ties your engineering work to a business outcome, so the last impression is concrete.

What to demonstrate

  • Mutual fit between you and the team
  • Alignment with team culture and its way of working

How to prepare

  • Review your notes from earlier rounds and write one stronger answer for the question you handled worst
  • Prepare thoughtful questions on onboarding, production support for store operations and the promotion path for engineers
  • Confirm remaining logistics: location, hybrid arrangement and start date

Where candidates lose points

Answering the slow-query question with "I would add an index" and stopping there.

Give a sequence: reproduce with the real parameters, capture the actual execution plan and SET STATISTICS IO output, and compare estimated with actual rows. Look for scans where seeks are expected, key lookups, non-sargable predicates (a function wrapped around a column, an implicit conversion such as NVARCHAR against VARCHAR), stale statistics and parameter sniffing. Only then choose a fix: rewrite the predicate, add a covering index with INCLUDE columns, update statistics. Close by verifying the before and after reads and naming the cost an extra index adds to inserts and updates on a busy inventory table.

Telling the most complex project in "we" from start to finish, so your own contribution never appears.

The reported question asks for your specific contribution. Spend a sentence on the team context, then switch to "I": the component you designed, the decision you made and the alternative you rejected, the code or migration you wrote, and a measurable outcome. Prepare the follow-up of what you would change now.

Treating the inventory-lag scenario as a capacity problem and jumping straight to more servers or a cache.

Ask clarifying questions first: which screens, which stores, what peak means, since when. Then measure wait statistics and blocking chains, long transactions holding locks on stock rows, reports running against the transactional database, and chatty N+1 API calls. Match each fix to a cause: shorter transactions, a reporting replica, an index for the hot lookup, caching only reference data like the product catalogue. Know two follow-ups. READ_COMMITTED_SNAPSHOT stops reports blocking writers, but not two writers contending for one stock row. And use a single atomic UPDATE ... SET qty = qty - @n WHERE item_id = @id AND qty >= @n instead of read-then-write, so registers cannot oversell.

Giving the same jargon-heavy answer to HR, a department head and a senior developer.

Candidates report meeting all of these audiences. Prepare each project in a business version (what improved for stores, deliveries or reporting, in numbers) and an engineering version. Practise the reported prompt about simplifying a complex technical process for a non-technical stakeholder until you can explain one system without acronyms.

Generic motivation and vague career goals in the leadership rounds.

Candidates report questions on long-term goals and an emphasis on promoting from within. An answer that would fit any employer weakens you here. Name what draws you to internal systems for a building-materials supplier serving professional builders, describe a realistic growth path on that kind of team, and back it with evidence from your own history of staying and taking on more.

Questions & practice

Diff a projection against the primary without per-row point reads

Hard · reconciliation · range hashing · throttling

The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.

Approach

  1. Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
  2. Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
  3. For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
  4. Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.

Follow-up

  • The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
  • Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?

Track a rolling failure rate per destination for circuit decisions

Easy · sliding window · ring buffer · circuit breaker

The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.

Approach

  1. Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
  2. Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
  3. State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
  4. Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.

Follow-up

  • The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
  • A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?

Identify the heaviest tenants in a five-minute window under memory pressure

Medium · top-k · heavy hitters · streaming

The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.

Approach

  1. Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
  2. Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
  3. State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
  4. Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.

Follow-up

  • The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
  • Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?

Hold a per-tenant active cap against concurrent creates

Hard · write skew · isolation levels · constraints

A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.

Approach

  1. Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
  2. Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
  3. Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
  4. State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.

Follow-up

  • A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
  • The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.

Approach

  1. Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
  2. Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
  3. For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
  4. State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.

Follow-up

  • A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
  • How do you honour an erasure request while resource_revision.actor_user_id still references this table?

Convert the listing endpoint from offset pages to stable cursors

Medium · pagination · cursors · index access path

GET /v1/resources returns a tenant's resources newest-updated first, today with page and per_page, backed by index (tenant_id, status, updated_at DESC, resource_id DESC). Callers are a browser feed and a nightly sync job that walks every page. Users report items appearing twice or vanishing between pages, and page 400 is slow. Design the cursor contract: what the cursor contains and how it is encoded, the exact WHERE and ORDER BY, what happens when a row's updated_at changes mid-walk, how a client detects the end, and what the sync job does when a cursor is rejected.

Approach

  1. Separate the two defects, because they have different fixes. Cost: OFFSET n makes the engine produce and discard n rows, so price grows with page depth rather than page size and page 400 pays for 400 pages of work. Correctness: while the set shifts, rows cross the offset boundary and are skipped or repeated, and nothing in the response lets the client detect it.
  2. Write the seek: WHERE tenant_id = $1 AND status = $2 AND (updated_at, resource_id) < ($k, $id) ORDER BY updated_at DESC, resource_id DESC LIMIT n. The tie-break is not decoration - updated_at is not unique, and two rows sharing a timestamp across a page boundary reintroduce exactly the skip this was adopted to remove. PostgreSQL seeks the composite index on the row-value comparison directly; on an engine that does not optimise a row constructor, expand it into the equivalent OR form or the plan quietly degrades to a scan.
  3. Encode the cursor as an opaque token carrying the sort key, the id, and a fingerprint of the filter and sort order, signed or at minimum validated. A cursor replayed against a different sort or filter must be a 400 with its own code, not a silently wrong page - the sync job cannot notice the difference otherwise.
  4. State the guarantee precisely rather than generously. Keyset is stable against inserts and deletes elsewhere in the set, because the position is a value and not a count. It is not a snapshot: updated_at is mutable, so a row that is edited during the walk re-sorts and may be seen twice or not at all. If the sync job needs exactly-once coverage, order on an immutable key such as (created_at, resource_id), or walk resource_revision by revision_id and treat updated_at as data.

Follow-up

  • The client wants a total count and the ability to jump to page 400. What can you honestly offer instead, and what does each option cost?
  • How do you paginate backwards, and what does that require of the index?

Make resource creation safe for a client that retried a timeout

Medium · idempotency · retries · race conditions

POST /v1/resources creates a resource row and, in the same transaction, a resource_revision and an outbox_event. A partner's HTTP client times out at 2 seconds and retries twice; under load the first request is still executing when the retry arrives, and duplicate resources are appearing. Using idempotency_key (tenant_id, idempotency_key, request_fingerprint, state, response_status, response_body, resource_id, locked_until, expires_at), specify the whole contract: first call, concurrent retry, retry after completion, a key reused with a different body, and what the client does with each response.

Approach

  1. Name why the plain REST shape fails here. POST is not idempotent, a timeout leaves the client unable to distinguish a lost request from a lost response, so it must retry - and the retry is a second create. Checking whether the key exists and then inserting it is check-then-act: under exactly the concurrency that caused the timeout, both attempts look, both see nothing, both proceed.
  2. Let the unique constraint arbitrate instead. INSERT the key row with state 'in_flight' and locked_until set to now plus the request deadline, and commit that reservation on its own before any work is attempted - an uncommitted row is invisible to the concurrent retry, which is the entire point of writing it first. One caller wins the insert and proceeds; the other takes the unique violation and reads the row rather than falling through to the work.
  3. Branch the loser on what it found. 'succeeded' replays response_status and response_body verbatim, with a header marking it a replay. 'in_flight' with locked_until in the future returns 409 and a Retry-After - do not park a request thread waiting on another request, that is how a concurrency bound is consumed by retries. 'in_flight' past locked_until is reclaimable by a conditional UPDATE predicated on the old locked_until, so exactly one reclaimer wins. A fingerprint mismatch is 422 and never the stored response: a different body under the same key is a client defect, and serving the old response hides it forever.
  4. Commit the effect and the key's completion together - resource insert, revision row, outbox row, and the UPDATE of the reserved key to 'succeeded' with the stored response, in one transaction. That makes a crash binary: either all of it is durable and the key reads 'succeeded', or the transaction rolls back and the only durable trace is the reservation row, still 'in_flight', with no resource behind it. The reclaim branch finishes that case - once locked_until passes, one retry takes the row by conditional UPDATE and runs the work for the first time. Splitting the two commits is what creates the window with no recovery: the effect durable, the key still 'in_flight', and a reclaimer that cannot distinguish that from work never started, so it creates a second resource.

Follow-up

  • Response bodies are pruned at 24 hours but resource_id is kept. What do you return to a replay that arrives after pruning?
  • The endpoint now also sends a welcome email. Which part of this contract prevents a second send, and which part cannot?

Listing latency scales with page size, not with filters

Easy · n+1 · latency · query batching

The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.

Approach

  1. Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
  2. Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
  3. Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
  4. Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.

Follow-up

  • The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
  • How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?

Suggested practice checklist

Each day targets one of the five reported rounds or one group of reported questions: screening and motivation, behavioral stories, the project deep dive, SQL tuning, the inventory-lag scenario, code quality and the stack, then a full in-person rehearsal.

Day 1 — Screening, motivation and stack mapping

  • Write your answer to "Why are you interested in working for a building materials company?" naming inventory, POS or delivery systems and the professional builders they serve.
  • Build a table mapping each listed requirement (C#/.NET or Java, SQL Server, database design, REST APIs, front-end framework, Git) to one project where you used it.
  • Write the questions for your recruiter: remote policy, travel to Eighty Four, PA, team stack, format of the Technical Evaluation.

Deliverable: A written motivation answer, a requirement-to-project table and a list of recruiter questions.

Day 2 — Reported behavioral stories

  • Write STAR stories for working with a difficult team member, handling critical feedback, and learning a new technology quickly to meet a deadline, each ending in a result and what you changed afterwards.
  • Write a story for simplifying a complex technical process for a non-technical stakeholder, with the explanation you actually used.
  • Say each story aloud and cut any part where the team acts and you do not.

Deliverable: Four written STAR stories, each with an explicit personal action and outcome.

Day 3 — Most complex project and the deadline trade-off

  • Write two versions of your most complex project: a business version for a department head and an engineering version for a senior developer, both naming your contribution.
  • Write your trade-off story: the business deadline, the options, what you gave up, how you contained the risk and whether you later paid it back.
  • Prepare a decision you reversed and an estimate made without prior experience; both are useful practice for the trade-off story.

Deliverable: Two project versions, one trade-off story and two extra stories, written out.

Day 4 — Slow-query tuning on SQL Server

  • Write your step-by-step answer to improving a slow-running database query: reproduce, actual plan, STATISTICS IO, sargability, statistics, parameter sniffing, index or rewrite, verify.
  • Work the listing-latency debugging question and the offset-to-cursor pagination question, noting how each shows up in a plan.
  • Write one non-sargable query and its rewritten form, plus a covering index with INCLUDE columns, and explain the write cost.

Deliverable: A written tuning sequence, two worked practice answers and one before-and-after query example.

Day 5 — Inventory lag at peak hours and data integrity

  • Outline your answer to a store manager reporting inventory lag at peak hours: clarifying questions, measurements, likely causes, fixes ranked by risk to stock accuracy.
  • Work the per-tenant active-cap concurrency question and relate it to many registers decrementing the same stock row.
  • Work the safe-retry creation question and relate it to a POS sale submitted twice after a timeout, then write your production-incident story.

Deliverable: An inventory-lag answer outline, two worked concurrency answers and one incident story.

Day 6 — Code quality, the stack and legacy integration

  • Write your answer to how you ensure code quality before production: tests at each level, code review, CI, staged rollout, monitoring and rollback.
  • Sketch a small REST endpoint in C# or Java with data access to SQL Server, including validation and error handling.
  • Work the soft-delete re-registration question using a filtered unique index, and outline how you would put a new API in front of existing business logic without changing its results.
  • Sketch a store-associate screen, such as a stock lookup or a delivery schedule, in your front-end framework. Cover how it shows loading and error states over a slow store connection and how it keeps data entry simple for non-technical users.

Deliverable: A written quality answer, one endpoint sketch, one worked SQL answer, a legacy-integration outline and one store-screen sketch.

Day 7 — Leadership round and full in-person rehearsal

  • Write your long-term career answer and one example of growth or tenure that supports it.
  • Run a mock of back-to-back conversations with a friend playing HR, a department head and a senior developer, using the two versions of your main project.
  • Write the questions you will ask per audience, including two on how the software supports stores, deliveries and Pros.

Deliverable: A career answer, notes from one multi-audience mock and a question list for each interviewer type.

Behavioural preparation

Candidates report that much of this loop is behavioral: teamwork, response to criticism, learning under pressure, motivation for the industry and long-term goals. Each story should show your own action, a result you can state concretely, and that you can work with non-engineers. The mistake that sinks these answers is a story with no personal decision in it or a motivation answer that would fit any company.

  1. Tell me about a time you had to work with a difficult team member. How did you handle it?
  2. Why are you interested in working for a building materials company?
  3. Describe a situation where you had to learn a new technology very quickly to meet a deadline.
  4. How do you handle feedback that is critical of your work?
  5. Describe a time you had to make a technical trade-off to meet a business deadline.
  6. Describe a time you had to simplify a complex technical process for a non-technical stakeholder.

Narrate an outage you owned from page to postmortem

Hard · incident response · blast radius · postmortems

Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.

Approach

  1. Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
  2. Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
  3. Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
  4. Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.

Follow-up

  • What would you do differently in the first five minutes, given the same dashboard and no more information?
  • Which follow-up action did you deliberately not take, and why was dropping it the right call?

Reverse your own decision and price the reversal

Medium · reversibility · measurement · migrations

Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.

Approach

  1. State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
  2. Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
  3. Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
  4. Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.

Follow-up

  • What in that decision was irreversible, and did you know it was irreversible when you made it?
  • How did you tell the people who had already built on top of the original decision?

Estimate work you have never done and defend the range

Hard · estimation · backfills · expand-contract

You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.

Approach

  1. Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
  2. Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
  3. Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
  4. Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.

Follow-up

  • How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
  • Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?