As a Software Engineer for the State of Arizona, you serve a critical function in maintaining and modernizing the digital infrastructure that supports millions of residents. This role is not just about writing code; it is about building reliable, secure, and efficient systems that power essential government services. Whether you are working on legacy mainframe environments or modern application development, your contributions ensure the stability of state operations.
The work environment at the State of Arizona is mission-driven and collaborative. You will often find yourself working alongside cross-functional teams to solve complex technical challenges that have a direct, tangible impact on the community. If you value stability, public service, and the opportunity to work on systems that form the backbone of state infrastructure, this role offers a unique professional environment where your technical expertise directly translates into public value.
Initial Screening
reportedHalf of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.
What to demonstrate
- Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
- Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
- Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not
How to prepare
- Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
- Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
- If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window
Panel Interview
reportedA day like this is several different games in a row, and the expensive mistake is carrying the previous one into the next room. Coding rewards narrow precision and finishing inside a timer. Design rewards breadth, stated assumptions and naming what you are deliberately not building. Behavioural rewards specificity about people and decisions. Candidates who over-engineer a coding problem they were supposed to finish, or who start sketching class hierarchies before anyone has agreed what the system has to do, are usually still playing the last round. Between rooms, name out loud which game the next one is.
What to demonstrate
- Whether the coding round ends with something that runs and has been traced against a degenerate input, rather than an extensible design that was never finished
- Whether a design discussion opens by agreeing on traffic shape, read-to-write ratio and what is allowed to be stale, instead of proceeding from an architecture you arrived with
- Whether a behavioural answer names a person, a disagreement and what you did about it, rather than describing the system the story happened inside
- Whether the opening habits still appear late in the day: restating the problem, asking for constraints, saying the plan before typing
How to prepare
- Book three mocks of different types back to back on one afternoon and ask each interviewer afterwards which round you answered in the wrong mode
- Write a three-line opening script per round type — coding: restate, name the approach and its cost, then type; design: ask for scale, read-write mix and what must not break; behavioural: name the person, the stakes and the decision — and run it off a card so the switch is mechanical rather than remembered
- Practise coding with a timer you do not extend, stopping when it stops, so the trained reflex is to finish a correct solution rather than to keep improving one
Reference Checks
reportedBecause the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.
What to demonstrate
- Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
- Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
- Whether you can be redirected in the middle of an answer without restarting it from the beginning
- Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it
How to prepare
- Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
- Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
- Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing
PracHub editorial advice for the preparation topics above.
Serialising a tenant's writes through select ... for update on a single counter row
It is the first change that makes a counter correct, and it caps that tenant's write throughput at roughly one divided by the lock hold time. A transaction that takes the lock, makes a network call and then commits holds it for the entire round trip: at 2 ms that is about 500 writes per second for the whole tenant, and the largest tenants are exactly the ones that exceed it. The damage then spreads, because every waiter holds a database connection while it queues, so one hot tenant drains the shared pool and the symptom presents as a site-wide latency incident rather than as a lock problem. The repairs are to shrink the critical section to a single statement, to shard the counter into per-(tenant, hour) or per-(tenant, bucket) rows and sum on read, or to batch in memory and flush periodically while accepting the bounded loss that batching implies.
Paginating a growing table with limit and offset
Two unrelated defects share the idiom. Correctness: rows inserted or deleted between page requests shift the window, so a consumer walking an export skips rows and sees others twice, which for a customer-facing sync is silent data loss rather than an error anyone notices. Cost: the database still produces and discards the skipped rows, so page N costs time proportional to N times the page size and a deep page on a large table degrades from milliseconds to seconds. Keyset pagination over a stable, unique, indexed ordering -- where (created_at, id) < ($1, $2) order by created_at desc, id desc limit $3 -- is constant-cost per page and immune to shifting, on the precondition that the cursor columns never change value for a row, which disqualifies updated_at as a cursor.
A queue or buffer with no bound
Every producer-consumer boundary needs a capacity and a policy for reaching it: block the producer, shed load, or drop the oldest entry. Unbounded buffering converts a temporary slowdown into memory exhaustion and hides the backpressure signal that would have revealed the consumer was falling behind.
Choosing a schema before the access patterns are known
Write the queries first, with their filters, sort orders, cardinalities and which ones sit on the latency-critical path, then design tables and indexes to serve them. An index nothing queries still costs write throughput and storage, and a hot query with no supporting index becomes a full scan that only hurts once the table is big.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Parse and verify a timestamped multi-signature webhook header
An inbound webhook carries a signature header of at most 1 KiB shaped t=<unix seconds>,v1=<64 hex chars>, with up to five v1 values during secret rotation and possibly unknown scheme keys. You hold the raw request body bytes and the currently active signing secrets. Write the parser and the verifier: accept when any active secret reproduces a signature and the timestamp is within a five-minute tolerance in either direction, reject otherwise. Single left-to-right pass over the header, no regular expression. State what is inside the MAC and why.
Approach
- Parse in one scan: split on
,, then on the first=only, since a value may itself contain=under a future scheme. Accepttexactly once and treat a secondtas a reject rather than last-wins. Push everyv1onto a short list and ignore any other key, so av2can be introduced later without breaking this verifier. - Say what is signed: HMAC-SHA256 over the exact byte string
<t>.<raw body bytes>, yielding 32 bytes or 64 hex characters. The timestamp sits inside the MAC because otherwise an attacker replays yesterday's body with its still-valid signature and only has to edit the header timestamp. - Hash the bytes as received. Verifying against a re-serialised JSON body is the usual defect: key order, whitespace and number formatting all change the bytes while the parsed objects compare equal, so signatures fail for honest senders and the popular 'fix' is to stop checking.
- Compare in constant time over fixed-length digests. Decode the hex to 32 bytes, accumulate
acc |= a[i] ^ b[i]across the whole length, and testacc == 0at the end. Evaluate every candidate without an early exit; at five candidates that is five HMACs over the body, linear in body size and negligible beside the network. - Apply the tolerance as a two-sided bound, rejecting when
|now - t| > 300seconds. A sender whose clock runs ahead of yours is an ordinary case, and an unbounded future timestamp is a free replay window. - Complexity: O(L) over the header producing k candidates, plus k HMACs at O(|body|) each. Space is O(k) beyond the body itself. Do the cheap rejections, including the tolerance check, before any cryptography runs.
Worked solution 15 min
- Write the grammar on one line before coding:
header := field (',' field)*,field := key '=' value, split on the first=only. - Implement the parser to return
{t: int, v1: [hex, ...]}, rejecting a missingt, a duplicatet, anyv1that is not 64 hex characters, and a header over 1 KiB, all before any cryptography runs. - Implement the verifier: for each active secret compute
HMAC-SHA256(secret, f'{t}.'.encode() + raw_body), compare it in constant time against each parsedv1, and OR the results with no early exit. - Test with a valid signature; the same body with
tmoved 400 seconds into the past; the same body witht400 seconds into the future; a header carrying an unknownv2=alongside a validv1; and a body re-serialised with different JSON key order.
Follow-up
- The body is 40 MB. What changes about where you verify, and what can you do before the whole body has arrived?
- A customer reports that signatures fail for exactly the requests whose body contains a non-ASCII character. What is your first hypothesis?
- How do you rotate the signing secret with no failed deliveries, and how long do both secrets stay live?
Fold a deduplicated usage stream into hourly rollups
You are given one day of usage_event rows, up to 250 million, each carrying event_id, tenant_id, workspace_id, environment, sku, quantity numeric(20,6), idempotency_key, occurred_at and ingested_at. Produce usage_rollup_hourly cells keyed (tenant_id, workspace_id, sku, hour_start) with quantity_sum, event_count and source_max_ingested_at. An event counts once per (tenant_id, idempotency_key). The rollup grain has no environment column, so state your filter. One pass. Give your time and space bounds, and say what the deduplication actually costs in memory.
Approach
- Bucket on
occurred_at, neveringested_at:hour_start = date_trunc('hour', occurred_at at time zone 'UTC'). The two columns answer different questions.occurred_atsays which hour the customer is billed for;ingested_atsays how current the fold is. Using the second for the first makes late data invisible instead of correctable. - The fold is trivial and the deduplication is the entire cost, so price it before designing anything clever. An exact set over
(tenant_id, idempotency_key)at 250M entries, stored as a 16-byte 128-bit hash in an open-addressed table at 0.7 load factor, needs about 357M slots at 16 bytes each, roughly 5.7 GB. The fix is partitioning byhash(tenant_id) % Pso each shard holds 1/P of the set and no tenant's keys straddle shards. - Rule out a Bloom filter as a replacement, in the right direction: a false positive reports 'already seen' for an event never seen, so you drop a real event and lose revenue with no error raised. It is usable only as a negative pre-filter in front of the exact set, where a miss is conclusive and a hit must fall through to the real lookup.
- Accumulate in scaled integers, not binary floating point.
numeric(20,6)admits values below 10^14, so one event scaled to micro-units can reach 10^20, past int64's 9.22 x 10^18; use a 128-bit or arbitrary-precision accumulator unless you first bound the per-event maximum. binary64 represents integers exactly only to 2^53, about 9.01 x 10^15, and cannot represent 0.1 at all, so two runs that sum in different orders disagree. - Carry
source_max_ingested_at = max(ingested_at)over the events folded into each cell, and countevent_countover accepted, post-dedup events. Without that watermark there is no way to prove later what a number did and did not include, which is the first question any reconciliation asks. - State the environment filter explicitly, because the rollup grain cannot record it. A fold that quietly includes
stagingbills non-production traffic; one that quietly excludes it loses a cost signal. Production-only is the billing answer, and either way it belongs in the job name and the output metadata. Complexity: O(n) time, O(distinct dedup keys) space, dominated by the dedup set rather than by the cells.
Follow-up
- A producer retries at 23:59:59 and the retry lands at 00:00:01. The unique index on the daily-partitioned table must include the partition key. What gets double-counted, and what is the smallest change that fixes it?
- The consumer acknowledges its batch before committing the fold. Which failure loses revenue now, and which arrangement duplicates instead?
- What makes a re-run over the same day produce byte-identical rollups?
Find peak concurrent sandbox usage from run intervals
Given up to 5 million job_run rows for one tenant over one day, with run_id, started_at, finished_at, status and wall_clock_limit_seconds, report the maximum number of sandboxes running at once, the earliest instant that maximum is reached, and the first run_id that would breach a per-tenant cap of C. started_at is null while a run is queued; finished_at is null both for runs still executing and for runs in status lost. Treat a run as occupying [started_at, finished_at). Give the complexity and state how you handle each null.
Approach
- Turn each run into two sweep events,
(started_at, +1)and(end, -1), then sort the 2n events by timestamp with-1ordered before+1at equal timestamps. That tie-break is what makes the interval half-open, so a run finishing at 10:00:00 and one starting at 10:00:00 never overlap. - Decide each null out loud before sweeping, because each choice moves the answer. A null
started_atmeans queued and contributes nothing. A nullfinished_atwith statusrunningorleasedis clipped to the window end. Statuslosthas no observed end at all, so clip it atstarted_at + wall_clock_limit_secondson the grounds that the supervisor owns the timeout, and record that you did. The table'scheck (finished_at is null or started_at is not null)guarantees you never see an end without a start. - Sweep once, maintaining a running counter, the maximum, and the timestamp at which the maximum was first attained (update
peak_atonly on a strict increase, or you will report the last such instant instead of the earliest). Capture the firstrun_idwhose+1takes the counter to C+1 during the same sweep rather than in a second pass. - Complexity: O(n log n) dominated by the sort, O(n) space. If rows already arrive ordered by
started_at, a min-heap of end times gives O(n log k) time and O(k) space with k the peak concurrency, which is the better shape when the rows come from an index scan on(tenant_id, started_at). - If second resolution is acceptable, counting-sort the endpoints into an 86,400-slot delta array and prefix-sum it: O(n + T) time and O(T) space, which beats the comparison sort at 5 million rows. It answers only at second granularity, so state which resolution the cap is defined in.
Follow-up
- Now report peak concurrency per tenant for 10,000 tenants from one globally sorted stream. What changes about memory and about the sort?
- The cap has to be enforced at dispatch rather than reported afterwards. What does the admission check look like, and where does it race?
- How would you answer 'peak concurrency within any 5-minute window' without re-sorting?
Paginate a tenant's delivery export without skipping rows
A customer exports webhook_delivery: delivery_id (bigint identity), subscription_id, tenant_id, event_id, status, attempt_count, next_attempt_at, created_at, delivered_at, updated_at. The endpoint runs select ... where tenant_id = $1 order by created_at desc limit 100 offset $2, and customers report rows missing from exports taken while new deliveries are being inserted. Write the replacement query and the index that supports it, paging a tenant's deliveries newest first at constant cost per page. State why updated_at cannot be the cursor column.
Approach
- Name the defect precisely. OFFSET is a position in a result set that is recomputed on every request, so a row inserted ahead of the window shifts everything back by one and the next page starts after a row the client never received. Nothing errors and no identifier gap appears, so the loss is silent.
- Replace the position with a value predicate over a stable, unique, indexed ordering:
where tenant_id = $1 and (created_at, delivery_id) < ($2, $3) order by created_at desc, delivery_id desc limit 100. The row comparison is load-bearing: created_at alone is not unique, so ties straddling a page boundary are dropped or repeated, which is the same bug in a smaller window. - Index
(tenant_id, created_at, delivery_id). PostgreSQL scans a btree in either direction, so an all-DESC ORDER BY is served by an ASC index read backwards and no DESC modifiers are needed; they only matter when the ORDER BY mixes directions. Confirm the plan has no Sort node above the index scan, or the LIMIT stops being an early exit. - Price both forms: keyset is one index descent plus 100 adjacent leaf entries per page, constant regardless of depth, while OFFSET still produces and discards every skipped row, so page N costs time proportional to N times the page size and a deep page on a large table goes from milliseconds to seconds.
- Rule out updated_at as the cursor from the precondition, not from taste: a cursor column must never change value for a row already paged past. updated_at moves on every delivery attempt, so a row the client already emitted re-enters a later page and is exported twice. created_at and delivery_id are immutable, which is the whole qualification.
Follow-up
- The client wants a snapshot as of one instant rather than a live tail. Compare a repeatable-read transaction held open, an added
created_at <= $snapshotbound, and a materialised export table. - A retention job deletes deliveries older than 90 days. What does a client mid-walk see, and does keyset pagination help at all?
- The customer wants to resume an export from yesterday's last cursor. What must be true of the cursor for that to be safe?
Find the join that inflates every invoice total
invoice_line_item holds line_id, invoice_id, tenant_id, sku, rate_tier, quantity, unit_price_micros, amount_minor (bigint), currency, kind, voided_at. invoice_payment_attempt holds attempt_id, invoice_id, tenant_id, amount_minor, status (succeeded, failed, pending), created_at, and an invoice has many attempts. A finance report runs select i.invoice_id, sum(l.amount_minor), count(p.attempt_id) from invoice i join invoice_line_item l using (invoice_id) join invoice_payment_attempt p using (invoice_id) group by 1 and the totals are wrong. Say precisely what the sum now equals, and write a version that is also correct for invoices with zero attempts.
Approach
- Compute what the query actually returns before fixing it. The two joins form a Cartesian product per invoice, so each line row repeats once per attempt row:
sum(l.amount_minor)is the true total multiplied by the attempt count, andcount(p.attempt_id)is attempts times lines. Three lines and two attempts report double the money and six attempts. - Reject the reflex repair.
count(distinct p.attempt_id)does fix the count, because attempt_id is unique.sum(distinct l.amount_minor)does not fix the sum, because two legitimate lines with equal amounts collapse into one. DISTINCT inside an aggregate deduplicates values, not rows, and the difference stays invisible until two lines happen to match. - Aggregate each branch to invoice grain before joining: one CTE summing lines by invoice_id, one counting attempts by invoice_id, then join the two results. A LATERAL subquery per invoice is equivalent and sometimes plans better when the outer set is small. Either way every aggregate stays at the grain it was defined at.
- Keep invoices with no attempts by making the attempt branch a LEFT JOIN with
coalesce(attempt_count, 0). An inner join here silently drops every unpaid invoice, which is usually the exact population finance is asking about. - Push each filter to its own grain:
where l.voided_at is nullbelongs inside the line CTE, not the outer query, or it would also filter the attempt branch through the join. Put the tenant predicate on both branches, since the denormalised tenant_id is what stops a wrong join crossing tenants. - Leave yourself a standing check: an invoice total is a function of its non-voided lines and of nothing about payments, so if changing the payment filter moves the money figure, the fan-out is back.
Worked solution 25 min
- Create one invoice with three lines of 1000, 1000 and 500 minor units and two payment attempts, then run the original query.
- Confirm it reports 5000 and 6 rather than 2500 and 2.
- Apply
sum(distinct l.amount_minor)and confirm the total becomes 1500, which is worse rather than better. - Write the two-CTE version with a LEFT JOIN and coalesce, and confirm 2500 and 2.
- Add a second invoice with lines and no attempts and confirm it still appears.
Follow-up
- Add a third branch for credit notes applied to the invoice. Does the CTE shape still hold, and when would a single pass with
filter (where ...)be better? - Over 500k invoices this report takes minutes. Which grain would you materialise, and how do you keep it correct when a line is voided?
- The same report is needed per tenant per month. What index makes the line CTE cheap?
Describe the usage of the End Transaction command.
Describe the usage of the End Transaction command.
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
What is the difference between PE and MU fields?
What is the difference between PE and MU fields?
Approach
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
A resumable usage export that never skips a row
Customers pull their own rows from usage_event through GET /v1/usage to reconcile against their own systems. The table is append-only and partitioned daily on ingested_at; a large tenant adds millions of rows a day while the export is being walked, and a client may pause for hours and resume. Specify the cursor, the index it requires, the tenant scoping, the ordering guarantee you can honestly offer, the per-page cost as the walk deepens, and what the client must do to avoid missing rows.
Approach
- Rule out LIMIT/OFFSET on two independent grounds and say both, because fixing only one leaves the other. Correctness: rows inserted between page requests shift the window, so a walking client skips rows and repeats others, which for a reconciliation consumer is silent data loss rather than an error anyone sees. Cost: the database still produces and discards the skipped rows, so page N costs time proportional to N x page_size and a deep page degrades from milliseconds to seconds.
- Use keyset pagination over a stable, unique, indexed ordering: WHERE tenant_id = $1 AND (ingested_at, event_id) > ($2, $3) ORDER BY ingested_at, event_id LIMIT $4, carrying the last row's pair as the cursor. The row-value comparison navigates a composite btree directly, so each page is O(log n + page_size) and stays constant as the walk deepens. The precondition is that the cursor columns never change value for a row, which ingested_at satisfies and updated_at would not.
- Lead the index with tenant_id - (tenant_id, ingested_at, event_id) - which is simultaneously the correctness guard and the plan choice. An index on (ingested_at) alone forces a filter across every tenant's rows, and on a table where one tenant holds most of them that is fine only for that tenant and terrible for everyone else. Because ingested_at is also the partition key, a resumed cursor prunes to the partitions from the cursor forward.
- Name the visibility hazard rather than assuming it away: in PostgreSQL now() is transaction start time, so a transaction that starts at T, inserts, and commits at T+8 s writes a row whose ingested_at is T but which becomes visible only at T+8 s. A walker that has already passed T never returns it. The gap equals the writer's longest transaction, so the mitigation is either a safety lag - serve only rows older than now() minus the longest permitted transaction - or a client that re-walks a trailing overlap window and deduplicates on event_id, which is stable and unique.
- Offer the guarantee you can actually keep: ordering by (ingested_at, event_id) with no claim whatsoever about occurred_at order, and completeness only behind the safety lag or with the documented overlap-and-dedup obligation on the client. Saying this in the API reference is part of the design, because the client's reconciliation logic is what has to absorb it.
Worked solution 20 min
- Write the keyset query and the exact index it needs, then read the query plan and confirm there is no Sort node.
- Insert rows concurrently while walking with a LIMIT/OFFSET pager and count the distinct rows returned against the rows that exist; repeat with the keyset pager and compare.
- Construct the out-of-order commit case by hand: open a transaction, insert, hold it open while the walker passes that timestamp, then commit, and check whether the walker ever returns that row.
- Choose the mitigation - safety lag or client overlap plus event_id dedup - and write down the number it depends on.
Follow-up
- The customer wants to reconcile by occurred_at instead of ingested_at. What breaks, and what would you offer them in its place?
- One tenant starts a full-history export. How do you keep it from occupying every connection in the pool?
- A customer reports a missing row. What do you check first, and what would each answer tell you?
Gateway p99 spikes on a five-minute cadence
edge-gateway caches each credential-to-authorisation-context decision for five minutes. p99 sits at 6 ms except for a spike to 900 ms roughly every five minutes, worst in the region with the most pods, and control-plane CPU and read latency rise in step with it. The error rate stays near zero. Customers are told a revoked credential stops authorising within 60 seconds. Give the ordered checklist that identifies the mechanism, and a fix that removes the spike without weakening the 60-second bound.
Approach
- Test periodicity before anything else: take the spike timestamps modulo the TTL in seconds. A tight cluster at a fixed offset means expiry phase, while traffic-driven spikes scatter.
- Overlay pod start times. Entries filled at first request inherit the phase of the pod that filled them, so a cohort of pods deployed together expires together and the amplitude should track cohort size rather than tenant count.
- Separate a herd from a capacity shortfall by measuring control-plane requests per second during a spike against baseline. A stampede shows a step of roughly (pods x hot keys) for one interval with hit rate collapsing to near zero, not a gradual climb that would indicate the dependency is simply undersized.
- Apply three independent controls: randomise each key's TTL by a factor drawn uniformly from something like 0.8 to 1.0 so cohorts de-phase; coalesce concurrent misses per key per pod so exactly one refresh is in flight; and serve the stale value while that refresh runs so a miss costs the stale read rather than the dependency's queue.
- Bound staleness against the published contract rather than against comfort: serve-stale is admissible only up to the 60-second revocation bound, so the TTL floor and the stale window together must stay inside it, and the published invalidation must delete the entry rather than schedule a refresh.
- Decide in advance what a miss does when the control plane is unreachable, because that is now the only uncached path: failing closed converts a dependency outage into a total outage, while extending stale service past the bound breaks the revocation promise. Pick one and configure it explicitly.
Follow-up
- Publish-subscribe invalidation is lossy under a partition. Given that, what actually enforces the 60-second bound, and what number would you put in the contract if asked to defend it?
- One tenant's key is hot enough that a single pod's coalesced refresh still matters. What changes?
- Would a shared cache tier in front of the control plane help or shift the problem, and what new failure does it add?
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Numbers before diagrams
- Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
- Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
- Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.
Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One system, from requirements to schema
- Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
- Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
- Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.
Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.
Practice prompt ↗Practice prompt ↗03The consistency you are actually buying
- Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
- Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
- Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.
Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.
Practice prompt ↗Practice prompt ↗04Failure is the design
- For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
- Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
- Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
- Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.
Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Scaling the hot path
- Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
- Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
- Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
- Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.
Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.
Practice prompt ↗Practice prompt ↗06Keep the coding hand in, at the bar that applies to you
- Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
- Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
- Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.
Deliverable: One problem hardened to review standard, with its test list and one written review comment.
Practice prompt ↗07Defend it while being interrupted
- Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
- Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
- Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.
Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
For anything that touched live traffic, be ready to say how you would have undone it: a flag, a staged rollout, dual writes with the old path still authoritative. Once the old column is dropped or the source rows are overwritten there is no reverse, so name what you kept a copy of and for how long.
Describe a situation when you were very close to missing an important …
Describe a situation when you were very close to missing an important deadline?
Approach
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Have you ever had to contradict your boss?
Have you ever had to contradict your boss?
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
- 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?
If management above your manager wants you to take a different approac…
If management above your manager wants you to take a different approach, how would you handle it?
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- 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?
- How did you know your change caused the improvement?
- 01
Describe a situation when you were very close to missing an important deadline?
- 02
Have you ever had to contradict your boss?
- 03
If management above your manager wants you to take a different approach, how would you handle it?
Is this an official State of Arizona interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at State of Arizona. Rounds and questions reflect what candidates have reported, not a process State of Arizona has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are the technical questions?
The difficulty depends on the specific role, but you should expect questions that test your day-to-day expertise. If you are a subject matter expert in the stack mentioned in the job description, you will find the questions fair and focused on practical application.
PracHub interview research ↗What is the best way to prepare for the panel interview?
Review the specific technologies listed in your job posting and prepare stories from your past experience that demonstrate how you handled deadlines or conflicts with management. Being able to explain your technical decisions clearly to a panel is a key differentiator.
PracHub interview research ↗How long does the hiring process typically take?
The process is generally efficient. Once you complete your interview, you can often expect communication regarding references or an offer within one to two weeks.
PracHub interview research ↗Is there anything I should do before the interview?
Yes, review your own past work and be prepared to speak about it in detail. Being able to explain your past projects and the specific technical challenges you overcame is highly valued by the interviewers.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22
