At Production Resource Group, a Software Engineer occupies a pivotal role at the intersection of high-stakes live event technology and robust software architecture. You are not just writing code; you are building the digital backbone that powers some of the world's most complex broadcast, video, and technical infrastructure projects. The work is dynamic, demanding, and requires a deep understanding of how software interacts with physical hardware and real-time production environments.
This position is critical because Production Resource Group operates in an industry where downtime is not an option. Whether you are working on broadcast video systems or optimizing technical infrastructure for power and lighting control, your contributions directly impact the reliability and success of global events. You will be expected to bridge the gap between abstract software concepts and the tangible, high-pressure requirements of the entertainment and broadcast technology sectors.
Given the nature of the industry, be prepared to discuss how your software solutions handle reliability, latency, and hardware integration in a real-world production context.
Initial Screening
reportedBefore anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Technical Discussion
reportedInput bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
PracHub editorial advice for the preparation topics above.
Letting the live playlist grow with the DVR window.
A sliding-window playlist carries one entry per segment, so a four-hour DVR at two-second segments is 7,200 entries - at roughly 100 bytes per entry, about 720,000 bytes - and every viewer refetches the document about once per target duration. At 100,000 concurrent viewers that is 720 kB x 100,000 every two seconds, roughly 288 Gbit/s of playlist text that is nearly identical between fetches. Delta playlist updates and byte-range addressing solve it, but they have to be designed in, not discovered once the playlist has become the largest object being served.
Shortening segment duration to cut live latency.
In classic segmented delivery a compliant client starts roughly three target durations back from the live edge, so halving the segment duration does halve that component - and that is the whole of the appeal. It also doubles the request rate for both segments and playlists, and doubles the number of IDR frames, which cost far more bits than the predicted frames around them, so quality at a fixed bitrate drops. Low-latency extensions exist precisely to break this coupling: they deliver sub-second parts within a segment that stays long enough to encode efficiently.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Quoting amortised or average cost as if it were a worst-case guarantee
Appending to a dynamic array is amortised O(1), but the append that triggers a resize copies every element, and hash lookup is constant only while the hash spreads the actual keys. Say which guarantee you are offering when the caller cares about the latency of one call rather than the total over many.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Deduplicate at-least-once heartbeats into per-session watch time
You receive a batch of up to 10^7 heartbeat records, each (session_id UUID, sequence INT, played_ms BIGINT, position_ms BIGINT, wall_clock). Delivery is at-least-once and out of order: a client that crashed and resumed replays earlier sequences, and an offline device can deliver days of buffered records in one burst. played_ms is a monotonically non-decreasing counter reported by the client; position_ms is the playhead. The batch spans roughly 10^6 sessions. For each session emit accumulated watch time and the count of distinct sequences received. Target O(n) time, and state your memory bound and what you do when it is exceeded.
Approach
- Split the output by idempotency before writing any code: max(played_ms) is idempotent under duplicate delivery, so watch time needs no deduplication at all. The distinct-sequence count is not idempotent, and it is the only figure that forces you to remember what you have already seen.
- One hash map keyed by session_id holding {max_played_ms, seen-sequence structure}, filled in a single pass. O(n) expected time, O(g) space for g distinct sessions.
- Bound the memory with real numbers rather than an asymptote: sequences are dense per session and start at zero, so a compressed bitmap per session costs order 100-200 bytes, and 10^6 sessions is roughly 100-200 MB. A bounded-reorder window would be cheaper but is wrong here, because a bulk offline sync arrives arbitrarily late.
- If the map does not fit, partition on hash(session_id) mod P and process shards independently. Every aggregate is per-session, so the partition is exact and no reduce pass is needed to merge across shards.
- Emit max_played_ms as watch time and the bitmap cardinality as the distinct count; a gap between cardinality and max_seq + 1 is a loss signal worth emitting alongside, not a reason to reject the session.
Worked solution 20 min
- Classify each required output as idempotent or not under duplicate delivery, and write down which ones therefore need stored state.
- Write the single-pass loop: map lookup, max update, bitmap set, no sorting anywhere.
- Compute the memory figure out loud: 10^6 sessions times the per-session structure, and compare it against the box you expect to run on.
- Describe the hash-partition fallback and argue why it needs no cross-shard merge.
- Construct a seek-heavy session by hand (forward 40 minutes, backward 10, forward 5) and show that the maximum is unchanged while a delta-based sum is not.
Follow-up
- A session's records arrive split across two batches a day apart. What has to persist for the per-session maximum to stay correct, and what does that cost per open session?
- played_ms decreases for 0.1% of sessions. Do you clamp, drop the record, or quarantine the session, and what does each choice do to payout counting downstream?
Audit segment boundary alignment across the catalogue without pairwise comparison
Every video rendition of a content version must share identical segment boundaries. Per content version you have 6-12 renditions, each with a sorted int64 list of segment start times in milliseconds, up to 3,600 entries for a two-hour title. The audit runs over 10^4 content versions a day. The obvious check compares every rendition against every other element by element, and it is correct. Give a method fast enough for the daily run, localise the first divergence when a version fails, and state the complexity of both.
Approach
- Price the naive version first, because it is correct and the argument has to be about cost: C(12,2) = 66 pairs times 3,600 elements is about 2.4 x 10^5 comparisons per version, so 2.4 x 10^9 a day. Embarrassingly parallel, and still CPU you do not need to spend.
- Replace pairwise equality with a canonical fingerprint: one streaming 64-bit hash per rendition over the boundary sequence, O(R * S) per version, then group renditions by fingerprint. Do the arithmetic rather than asserting a win: 12 x 3,600 = 4.32 x 10^4 element hashes per version, so 4.3 x 10^8 a day against 2.4 x 10^9 comparisons. That is 5.5x, not an order of magnitude, and 5.5 is not a remembered constant - it is (R - 1) / 2, the R^2 to R factor, which falls to 2.5x on a six-rung ladder. Per element a hash mix also costs more than an int64 compare, so treat 5.5x as an upper bound on element touches, not on wall clock.
- The durable reasons to prefer the fingerprint are structural rather than that ratio: each list is read exactly once so renditions never need to be resident together, and 8 bytes per rendition persists, which is what lets the check run incrementally as rungs become ready instead of as a batch. Say which direction can lie: a mismatch is always a real difference, while equality is equality up to collision probability, which is what you are trading away.
- Localise a failure with prefix hashes: build a prefix-hash array per rendition in O(S), then binary search for the lowest index at which two renditions' prefix hashes differ. O(log S), about 12 probes instead of a 3,600-element scan.
- Check the stronger property at publish time, since equal lists do not prove the boundaries are legal. Frames per segment = segment_duration_ms * frame_rate / 1000, and gop_size_frames must divide it. At exactly 25 fps a 6,000 ms segment is 150 frames and GOP sizes 25, 50 or 75 divide it cleanly. Do the NTSC case in the rational rate, not in the stored column: the true rate is 24000/1001 fps, so a 6,000 ms segment is 144000/1001 = 143.86 frames and can never align, while 6,006 ms is exactly 144 frames. The frame_rate_milli_fps column stores 23976, a rounding that drifts about 1 ms per 1,000 s (roughly 7 ms over a two-hour title) and computes that same aligned segment as 143.999856 frames.
Follow-up
- The audit is green but one player stalls switching from rung 4 to rung 5 at 00:41:12. What do you check that list equality cannot tell you?
- Run the audit incrementally as each rendition reaches status ready rather than as a nightly batch. What do you persist per content version, and what does a rung that arrives a day late cost you?
Recompute the downstream job set after a single rendition re-encode
The packaging pipeline for one content version is a DAG of up to 10^5 jobs and 5 x 10^5 edges: source validation, per-chunk encodes, per-rendition concatenation, packaging, encryption, manifest generation, publish. QC fails one video rung, so its chunk encodes are re-run on a different encoder build. Given the edge list and a set of dirty job ids, return every job that must re-run plus a valid execution order for them, and report whether the graph contains a cycle. Target O(V + E) time.
Approach
- Build forward adjacency (job -> dependents) in one O(E) pass over the edge list. No sorting is required anywhere in this problem.
- Collect the affected set S by BFS or DFS from the dirty set over forward edges, marking a visited bitset. O(V + E), each node and edge touched once.
- Order S with Kahn's algorithm run on the induced subgraph, recomputing in-degrees counting only edges whose tail is also in S. Reusing whole-graph in-degrees is the failure here: clean predecessors outside S leave phantom in-degrees, the ready queue starts empty and nothing is ever emitted.
- Detect the cycle from Kahn's own termination condition: if emitted count < |S|, the residual nodes contain a cycle and everything it feeds. Report the residual set rather than asserting, because a malformed encoder profile is a data bug, not a crash.
- State the trade-off against re-running the whole version: the affected set is one rung's chunks plus concatenation, packaging, encryption, manifest and publish, so you save the 6-12x ladder fan-out. But manifest and publish are common descendants of every rung and are therefore always in S, which is precisely why re-encoding one rung is not a local change.
Follow-up
- The re-encode uses a different encoder build. Which additional nodes become dirty, and why is encoder_build_id pinned per asset rather than taken from the pipeline default?
- Two rungs fail QC concurrently and two invalidation runs overlap. What makes the recomputation idempotent so the second run does not restart jobs the first already completed?
Withdraw a rights window and keep a reconstructable history
availability_window(id, content_version_id, territory_code, licence_type, starts_at, ends_at NULL, status ENUM('draft','active','withdrawn'), updated_at, updated_by) is edited by operators rather than by deploys. Six months later a rights dispute asks what a row said at 14:00 on a given day and who made it say that; updated_at and updated_by hold only the most recent edit. Design the history table and the capture mechanism, decide whether withdrawal is a status change or a DELETE, and write the query that reconstructs every window that was active for a territory at a past instant.
Approach
- Split the two jobs. Current state stays in
availability_window, which the serve path reads at roughly 10^4 QPS and must never join to history. History goes inavailability_window_history(window_id, revision INT, <every column of the base row>, operation ENUM('insert','update','delete'), changed_by, change_reason, valid_from, valid_to NULL, PRIMARY KEY (window_id, revision)). - Snapshot the whole row, not a diff. A diff makes 'state at T' a replay from revision 1 — O(revisions) per row, and wrong for any row whose early revisions predate the audit table. A full snapshot makes the same question a single range predicate.
- Capture with an AFTER INSERT OR UPDATE OR DELETE row trigger rather than in application code. This table's writes are rare, bursty and operator-driven, and a real correction is often an ad-hoc UPDATE run by hand against the database — which never passes through the application and so never writes history if the application is what does the capturing.
- Make withdrawal a status change, never a DELETE.
license_grantandqualified_playrows describe what was actually served, and 'no row exists' is indistinguishable from 'no row ever existed'. Protect the serve path from the resulting filter debt with a partial indexWHERE status = 'active'and a view that carries the predicate, so a forgottenstatus <> 'withdrawn'cannot serve a withdrawn window. - Close the interval on write: the trigger sets the previous revision's
valid_toto the new revision'svalid_from, half-open, so reconstruction isWHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)rather than a correlated 'latest revision before :t' subquery. Index it on (window_id, valid_from), plus (territory_code, valid_from) if audits filter by territory. - Keep the two time axes apart in the reconstruction query: valid time is when the row said what it said, and
starts_at/ends_atis the interval the row describes. Select the revisions live at:t, then apply the serve-time predicate to that snapshot. Conflating them answers a different question that still looks plausible.
Follow-up
- An operator edits the same window twice inside one transaction. How many revisions should exist, and how many does your trigger produce?
- The dispute asks not what the row said but what was actually served. Which table answers that, and what do you owe if the two disagree?
- How would you detect that the trigger was disabled for a bulk load?
Put segment duration where alignment cannot drift
content_version(id, title_id, duration_ms, encoder_build_id, packaging_status) and rendition(id, content_version_id, stream_kind ENUM('video','audio','subtitle'), width, height, frame_rate_milli_fps, segment_duration_ms, gop_size_frames, status). Every video rendition of a content version must have identical segment boundaries, and each segment must begin at an IDR frame, so gop_size_frames has to divide the segment length in frames. Both facts currently live per rendition and nothing stops a re-encoded rung from disagreeing. Restructure so the database rejects drift, and say what breaks at a frame rate of 24000/1001 with a 2,000 ms segment.
Approach
- Identify the functional dependency. For video rows,
segment_duration_msis determined bycontent_version_id, not byrendition_id. A dependency stored at the wrong grain is exactly what lets two rows disagree, and the invariant is presently a sentence in a comment rather than anything the database can refuse. - Move the authoritative value to the parent as
content_version.segment_frames. Ask whether the child copy earns its keep: the manifest build already loads the content_version row to resolve the URL, so the join costs nothing and the denormalised copy buys no read. If it is kept anyway for write-path convenience, guard it with a composite foreign key —UNIQUE (id, segment_duration_ms)on the parent andFOREIGN KEY (content_version_id, segment_duration_ms) REFERENCES content_version(id, segment_duration_ms)on the child. Under the default MATCH SIMPLE semantics a NULL in either child column satisfies the constraint, which is the escape hatch that lets audio and subtitle rows carry no video segment length. - Fix the representation before writing any CHECK. 24000/1001 is 23.976023976… and is not representable in integer thousandths, so
frame_rate_milli_fps = 23976is already a different number from the real rate and every derived value drifts from it. Store the exact rational asframe_rate_num INTEGER, frame_rate_den INTEGER— 24000 and 1001 — and store the segment length in frames rather than milliseconds. - With frames and an exact rational, both invariants become integer CHECKs that cannot round:
CHECK (segment_frames % gop_size_frames = 0)for IDR alignment, and the millisecond duration becomes derived —segment_frames * 1000 * den / num— rather than a second stored copy to keep in sync. - Work the stated case: at 24000/1001, 2,000 ms is 48000/1001 frames, about 47.952, so no segment of that target length contains a whole number of frames and no integer GOP size divides it. 96 frames is exactly 4.004 s and admits any GOP size dividing 96. The encoder has to round a millisecond target to whole frames, and that rounding is where two rungs encoded at different times end up on different boundaries.
- Accept what the schema still cannot catch. Rate control can relocate an IDR without changing any stored column, so add a post-packaging QC step that compares segment counts and first-sample timestamps across the video renditions of a version and fails the publish on a mismatch, and pin
encoder_build_idper rendition so a rung re-encoded on a newer build is a visible difference rather than an inference.
Worked solution 30 min
- Work the frame arithmetic for 25, 30000/1001 and 24000/1001 at 2,000 ms and at 4,004 ms, marking which combinations yield a whole number of frames.
- Draft both schemas — milliseconds on the child with a composite FK, frames on the parent — and write the CHECK constraints each one admits.
- Insert two video renditions of one content version with different segment lengths and confirm the second is rejected.
- Insert an audio rendition with a null video segment length and confirm it is accepted.
- Write the post-packaging QC query that compares segment counts across the video renditions of one version and returns the offending rendition ids.
Follow-up
- One rung fails QC and is re-encoded alone. What do you verify before republishing the manifest, and what is the rollback if boundaries moved?
- Audio renditions legitimately use a different segment length from video. Which of your constraints still applies and which has to be weakened?
- How do you migrate the existing millisecond column to frames without a window in which neither constraint holds?
How do you ensure your code remains stable under high-load production …
How do you ensure your code remains stable under high-load production environments?
Approach
- Design the error taxonomy before the success shape; callers branch on it.
- Say who the caller is and what they do when the call fails halfway.
- Define the identity of a request so a retry cannot double-apply it.
Follow-up
- What does a partial failure look like to the caller?
- How does a client discover it is on an old version of this contract?
How do you approach debugging complex systems where software interacts…
How do you approach debugging complex systems where software interacts with physical hardware?
Approach
- Separate accepted, pending, failed and confirmed; they are different facts.
- Say who the caller is and what they do when the call fails halfway.
- State how the contract changes without breaking existing clients.
Follow-up
- What happens if the caller retries after a timeout?
- How does a client discover it is on an old version of this contract?
Can you explain how you have handled multithreading in your previous p…
Can you explain how you have handled multithreading in your previous projects?
Approach
- Separate accepted, pending, failed and confirmed; they are different facts.
- Say who the caller is and what they do when the call fails halfway.
- Design the error taxonomy before the success shape; callers branch on it.
Follow-up
- What does a partial failure look like to the caller?
- How does a client discover it is on an old version of this contract?
Walk me through your most challenging project and the specific technic…
Walk me through your most challenging project and the specific technical hurdles you overcame.
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
What is your process for testing and validating software before it is …
What is your process for testing and validating software before it is deployed to a live environment?
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Hold the concurrent-stream limit with one atomic lease operation
An account has a plan limit of concurrent streams. Session starts run 10,000 per second at peak against 1,000,000 concurrent sessions, each renewing its lease every 10 seconds. Two devices in one household can start within the same millisecond, and a client that crashes never releases its slot. Using one lease store, give the exact acquire operation, the data structure and key, the lease TTL with the arithmetic behind it, what a renewal returns when the lease has already expired, and what the client does with that answer.
Approach
- Pick a structure that makes expiry part of the read. A sorted set per account, key lease:{account_id}, member session_id, score expiry in epoch milliseconds, lets one operation drop the dead members and count the live ones. Storing a plain counter forces a second structure to know which session holds which slot, and the two drift.
- Make acquire one server-side operation: ZREMRANGEBYSCORE key 0 now, then ZCARD, then ZADD only if the count is under the limit, all inside a single Lua script. The script runs to completion without interleaving, so two simultaneous acquires at the limit cannot both observe the pre-add count. Any design where the application reads the count and then writes is a lost-update race that two devices starting together will win together.
- Make renewal its own script: return lease-lost when ZSCORE for that session is nil, and otherwise write the new expiry score. Expiry has already removed a member that missed too many heartbeats, so a renewal after a network gap fails and the client must re-acquire, which re-checks the limit. A blind ZADD re-adds the evicted session and quietly takes the account over its plan, which is the same lost-update bug as the naive acquire wearing different clothes.
- Derive the TTL instead of picking one. TTL = k x heartbeat interval sets both failure modes at once: a viewer is evicted only after k consecutive missed heartbeats, and a crashed client strands a paid slot for at most the TTL. At 10s heartbeats, TTL 30s tolerates two misses and caps the stranded slot at 30 seconds. Note that heartbeat losses are correlated rather than independent, since one access-network blip takes all three, so any p^k eviction estimate is optimistic.
- Bound the keyspace and the hot key. PEXPIRE the whole account key to TTL plus slack so accounts that stopped streaming do not accumulate forever, and release explicitly with ZREM on a clean stop so the common case does not wait out the TTL. Keying by account_id puts all of an account's leases on one shard, which is what makes the script atomic; at a limit of four, that key sees single-digit operations per second, while the fleet total is 1,000,000 / 10s = 100,000 renewals per second spread across shards by account hash.
Worked solution 20 min
- Write the acquire script line by line and mark the point where a non-atomic version would interleave.
- Write the renewal script and state what it returns in the expired case and what the client does with that answer.
- Fix a heartbeat interval, derive the TTL from it, and state the false-eviction condition and the stranded-slot ceiling as numbers.
- Compute fleet-wide renewal operations per second and the per-key rate, then choose the shard key.
- Add the clean-release path and the key-level expiry, and say what each one prevents.
Follow-up
- The lease store loses a shard for 30 seconds. Do you fail open and let sessions start unchecked, or fail closed and block them, and what does each cost?
- A viewer switches from wifi to cellular and the app backgrounds for 45 seconds. Trace what they see, and change the design so it is not an eviction.
- Downgrade a plan from four streams to two while four streams are playing. Which sessions stop, when, and who decides?
Concurrency limit over-admits only during live-start bursts
Support reports accounts playing four concurrent streams on a two-stream plan, a few hundred times a day, clustered in the minutes after a scheduled live start. It never reproduces locally or in load tests below 500 starts per second. Acquire reads the account's active-lease count from the lease store and, if it is under the limit, adds a member with a TTL; heartbeats renew by removing and re-adding the member; clients retry a start that times out. Give the ordered checklist, the acquire primitive you would write, and the test that proves it.
Approach
- Rank hypotheses against the timing evidence before instrumenting anything. Read-then-write is a time-of-check-to-time-of-use race and explains clustering at bursts. A count read from a replica explains cases correlated with one region. A retry that mints a fresh session id explains excess leases that share a device but differ in session id. All three can be live at once, which is why one fix often fails to close it.
- Instrument the decision inputs rather than the outcome: observed count, limit, whether the member was added, which lease-store node served the read, and the client attempt number. One burst of these logs settles which mechanisms are firing and in what proportion.
- Close the check-then-act window first, because it is the one that scales with load. Make acquire a single server-side atomic operation over a sorted set keyed by account, with score equal to the lease expiry instant: in one script, ZREMRANGEBYSCORE up to now, then ZCARD, then ZADD only if the count is below the limit, returning acquired or refused. Cost is O(log N + M) for M expired members, and it cannot interleave because the script runs to completion on a single node.
- Close the renew hole second. Remove-then-add is two steps with a window in which the member is absent, so a concurrent acquire sees a free slot; under a burst that window is hit constantly. Renew must be one ZADD XX that updates the score of an existing member only, and must fail rather than re-create a member that has already expired.
- Close the retry hole third. The session id is client-generated, so a retry must reuse it and acquire must be idempotent on (account_id, session_id): re-acquiring an id already held refreshes the score and returns success without consuming a second slot. Without this, every timeout at a live start manufactures a second lease.
- Prove it with a test that fires limit + K concurrent acquires against one real lease-store instance and asserts exactly limit successes, repeated enough times that interleavings are likely, plus a test that renewing after expiry fails instead of resurrecting the member. A test built on a process-local mutex proves nothing, because the race is across service replicas.
Follow-up
- What lease TTL do you pick against a 10-second heartbeat, and what are the false-eviction and stranded-slot numbers that follow?
- How does the slot come back when a client crashes without sending a stop?
- The lease store fails over mid-burst: do you fail open or fail closed, and who pays for that choice?
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Rebuild the primitives by implementing them
- Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
- Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
- For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.
Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Arrays under an invariant: two pointers, sliding window, binary search
- Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
- Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
- Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.
Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.
Practice prompt ↗Practice prompt ↗03Sorting, heaps, and the greedy argument that has to be proved
- Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
- Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
- Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.
Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.
Practice prompt ↗Practice prompt ↗04Recursion, memoisation, and the step to a table
- Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
- Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
- Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.
Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Graphs, where most of the work is choosing the traversal
- Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
- Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
- Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.
Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.
Practice prompt ↗Practice prompt ↗06One day for everything that is not an algorithm
- Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
- Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
- Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.
Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.
Practice prompt ↗Practice prompt ↗07Solve out loud, under time
- Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
- Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
- Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.
Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.
Can you describe a time you had to optimize code to improve real-time …
Can you describe a time you had to optimize code to improve real-time performance?
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
What is your experience with Objective-C or similar languages in an em…
What is your experience with Objective-C or similar languages in an embedded or systems-level context?
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Describe a situation where you had to collaborate with non-technical s…
Describe a situation where you had to collaborate with non-technical stakeholders to deliver a solution.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
- 01
Can you describe a time you had to optimize code to improve real-time performance?
- 02
What is your experience with Objective-C or similar languages in an embedded or systems-level context?
- 03
Describe a situation where you had to collaborate with non-technical stakeholders to deliver a solution.
Is this an official Production Resource Group interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Production Resource Group. Rounds and questions reflect what candidates have reported, not a process Production Resource Group has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the interview process typically take?
The process is often efficient, sometimes moving from initial contact to an onsite interview within a few days. Be prepared to move quickly once you are in the pipeline.
PracHub interview research ↗What is the most important trait for a successful candidate?
Practicality. Being able to solve real-world problems under pressure is valued far more than theoretical knowledge or academic pedigree.
PracHub interview research ↗Is there a heavy emphasis on coding tests?
The focus is typically on your experience and how you handle debugging and system design rather than algorithm-heavy whiteboard coding. Be ready to discuss your past projects in detail.
PracHub interview research ↗What is the work culture like?
It is a fast-paced, mission-critical environment. You will be working with people who are passionate about the technical side of live events, and the culture rewards those who are proactive and reliable.
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