cswork / libraryOpen practice workspace

Interview guides · Fox

Fox Software Engineer Interview Guide

Fox Software Engineer interview guide: reported rounds, questions and how candidates describe the process.

The 60-second brief

As a Software Engineer at Fox Corporation, you are at the intersection of world-class media production and high-scale digital engineering. You will be responsible for building, maintaining, and scaling the platforms that deliver news, sports, and entertainment to millions of viewers globally. Your work directly impacts how audiences experience real-time content, meaning that performance, reliability, and low-latency delivery are core to your daily mission. This role is critical for Fox as the company continues its digital transformation. You will contribute to a complex ecosystem involving streaming infrastructure, data-driven content delivery, and robust backend services. Whether you are optimizing existing APIs or architecting new features, you are helping to shape the future of media consumption.

This guide is scoped to a Software Engineer candidate at Fox.

Focus areas

  • Data Structures & Algorithms (DSA)
  • Binary Trees
  • Tree Construction from Level Order Traversal

The Fox interview process

Fox candidates report 5 rounds over 4-6 weeks. The stages below are what candidates describe, not a published process.

Application Review

Initial review of your application to assess qualifications and fit for the role.

What to demonstrate

  • Initial review of your application to assess qualifications and fit for the role
  • Depth in Data Structures & Algorithms (DSA)

How to prepare

  • Be able to walk your CV end to end in two minutes, and say why this company specifically.
  • Have your salary expectations, notice period and location constraints ready, and ask for the rest of the loop in writing.

Screening Round

Focus on technical capabilities, emphasizing Data Structures and Algorithms.

What to demonstrate

  • Focus on technical capabilities, emphasizing Data Structures and Algorithms
  • Depth in Data Structures & Algorithms (DSA)

How to prepare

  • Answer aloud and timed: Explain the time and space complexity of your tree construction approach.
  • Answer aloud and timed: How would you optimize the validation process for an extremely deep tree?

Technical Implementation

Prepare for immediate technical implementation challenges during interviews.

What to demonstrate

  • Prepare for immediate technical implementation challenges during interviews
  • Depth in Data Structures & Algorithms (DSA)

How to prepare

  • Work Data Structures & Algorithms (DSA) until you can explain it without notes
  • Work Binary Trees until you can explain it without notes

Evaluation

Rigorous assessment focused on practical application rather than theoretical trivia.

What to demonstrate

  • Rigorous assessment focused on practical application rather than theoretical trivia
  • Depth in Data Structures & Algorithms (DSA)

How to prepare

  • Work Data Structures & Algorithms (DSA) until you can explain it without notes
  • Work Binary Trees until you can explain it without notes

Final Decision

Receive the final decision regarding your application status.

What to demonstrate

  • Receive the final decision regarding your application status
  • Depth in Data Structures & Algorithms (DSA)

How to prepare

  • Work Data Structures & Algorithms (DSA) until you can explain it without notes
  • Work Binary Trees until you can explain it without notes

Where candidates lose points

Think Aloud

Your interviewer is more interested in your problem-solving process than the final code. Narrate your thoughts clearly as you work.

Test Your Code

Always dry-run your solution with a sample input before declaring it complete.

Ask Clarifying Questions

Never start coding until you are certain you understand the constraints of the problem.

Questions & practice

Given the level order of a binary tree, construct a binary tree.

Medium · Data Structures and Algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.
  4. State the target complexity and say which constraint rules the naive version out.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

Validate a Binary Search Tree (BST).

Medium · Data Structures and Algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.
  4. State the target complexity and say which constraint rules the naive version out.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

Explain the time and space complexity of your tree construction approach.

Medium · Data Structures and Algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.
  4. State the target complexity and say which constraint rules the naive version out.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

How would you optimize the validation process for an extremely deep tree?

Medium · Data Structures and Algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.
  4. State the target complexity and say which constraint rules the naive version out.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

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?

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?

Choose what to break when replication lag reaches forty seconds

Hard · replication lag · read-after-write · consistency vs availability

Reads are served from two replicas: 14k requests/second, about 85% absorbed by cache, so roughly 2.1k reads/second reach the database. Writes go to the primary at 1.2k/second. A tenant's backfill drives replication lag from under 100 ms to 40 seconds and it is still climbing. Sessions that have just written are pinned to the primary. Decide, endpoint class by endpoint class, whether to serve stale, fail, or route to the primary, and justify each choice with the load it adds to the primary. Then state what you would have built beforehand.

Approach

  1. Establish blast radius before cause, because mitigation and diagnosis have different deadlines. The decisive arithmetic is what happens if the database reads move to the primary: 2.1k reads/second on top of 1.2k writes/second roughly triples its operation count, on the node already absorbing the backfill that caused this. Reads and writes are not equal in cost, so treat that as an argument against a blanket move rather than as a capacity model - but it is enough to rule out routing everything to the primary.
  2. Classify endpoints by what staleness costs, not by how important they feel. Reads whose staleness is invisible - listings, search, counters - stay on the replica and return the watermark so the client can tell. Reads that immediately follow that same session's write keep their primary pin, which is a small bounded slice of traffic rather than the whole 2.1k/second. Reads that feed a decision with a side effect - authorisation, quota, the read half of a read-modify-write - must not be stale at all, because a 40-second-old permission row is the stale-permission failure wearing a different costume; those go to the primary or fail.
  3. Shed instead of queueing. If the must-be-fresh class alone exceeds the primary's headroom, refuse its lowest-value slice with 503 and a retry-after. A request queued behind a saturated primary holds a connection for a client that has already given up, and the retry storm that follows is what turns degradation into an outage. Bound the connection pool per role so the read fallback cannot consume the write path's connections - that bulkhead is the single decision that determines whether writes survive the next ten minutes.
  4. Attack the cause in parallel, since it is the one thing that can be stopped. The backfill is the load generator. A backfill that reads replication lag as its throttle signal and pauses above a threshold would have made this a non-event, with batch sizes small enough that each batch's write volume is a fraction of what a replica can apply per second. That is most of the answer to what should have existed beforehand.

Follow-up

  • Lag returns to normal in nine minutes. Which mitigation do you remove first, and which one stays permanently?
  • A user reports their change did not save, and the write committed. Trace the path that produces that report and name the signal that would have shown it before the report arrived.

Keep one tenant's bulk export from starving projection updates

Medium · backpressure · fair scheduling · head-of-line blocking

The worker fleet runs about 600 jobs/second out of job_run, claimed under a lease and heartbeated while running. Most handlers finish under 200 ms; exports run for minutes. One tenant enqueues 50,000 exports. Design the scheduling so that tenant's exports slow down and nothing else does: the queue split, the concurrency caps and where they are enforced, the claim query, and the backpressure signal that stops accepting new work. State the in-flight worker count each class needs at its arrival rate, and what the cap costs the tenant that hits it.

Approach

  1. Do the concurrency arithmetic first, because it determines the pool shapes. Required in-flight work is arrival rate times service time: 600/second of 200 ms handlers needs about 120 workers, while one export per second at 180 seconds needs 180 on its own. The long tail dominates any pool it shares, so a single pool sized from the mean is consumed by exports while 50 ms projections queue behind them. That is head-of-line blocking, and adding capacity does not fix it because the ratio is what is wrong.
  2. Split by job class rather than by priority. Priority inside one pool still lets a running export hold its worker for the next four minutes - there is no preemption for a handler already executing. Separate queues and separate worker processes give each class a floor the other cannot take.
  3. Enforce the per-tenant cap inside the claim, not at enqueue. Claim with SELECT ... WHERE status='queued' AND run_after <= now() AND job_type=$1 ORDER BY run_after, job_run_id FOR UPDATE SKIP LOCKED LIMIT 1, restricted to tenants currently under their cap. Derive the running count from rows in status 'running' with a live lease rather than from a counter incremented at claim and decremented at completion: the derived count self-heals when a worker dies, and the counter leaks a slot every time one does.
  4. Make the ordering fair over tenants instead of over jobs. Strict FIFO with a cap still forces the claim to scan past 50,000 rows belonging to a capped tenant before it finds anyone else's work, so cost grows with backlog depth. Pick the tenant first from a small per-tenant queue-depth summary, then claim within that tenant, which makes the scan proportional to the number of active tenants instead.

Follow-up

  • An export outruns its lease, so two copies run. What must the handler do to make the second copy harmless, and what does it write to guarantee that?
  • With a cap of 4, the tenant's 50,000 exports now take hours. What do you tell them, and does the answer change if they are the largest tenant on the platform?

One customer endpoint stalls deliveries to every other destination

Medium · bulkhead · timeouts · saturation

The egress service delivers about 1.5k webhooks/second across 40,000 destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. Throughput falls to 300/second, queue depth climbs, and p99 delivery latency for unaffected destinations goes from 200 ms to minutes, while the error rate barely moves. One tenant holds 900 destination rows whose URLs share a hostname that now answers in 9.5 seconds. Explain the mechanism with the arithmetic, then give the containment in the order you would apply it.

Approach

  1. Look at saturation before errors. A flat error rate with collapsing throughput says nothing is failing, things are waiting, so the first signal to pull is in-flight request count or pool wait time rather than the error counter. This is the distinction that decides the whole investigation.
  2. Group in-flight work by resolved host, not by destination id. The cap is keyed per destination row, so 900 rows sharing one hostname buy 3,600 concurrent slots against a single host, each held for 9.5 seconds. The bulkhead was never a bulkhead for that host, and grouping by the wrong dimension is why the dashboard looked healthy.
  3. Do the arithmetic in both directions. Required concurrency is arrival rate times latency, so 1.5k/second at 200 ms needs about 300 in flight, which is entirely consumed by 3,600 slow slots; conversely whatever concurrency is left sustains rate equals concurrency divided by 9.5 seconds, which is the 300/second you are seeing. Matching both numbers is what promotes this from a plausible story to the mechanism.
  4. Explain why the circuit breaker never helped. It opens on consecutive failures, and a 9.5-second response inside a 10-second timeout is a success. Slow is not failing, so an error-rate breaker cannot see this; you need a slow-call ratio, a deadline propagated from the caller's remaining budget, or a concurrency limiter.

Follow-up

  • The host recovers to 80 ms. How long does the queue take to drain, and what does the drain do to the recovered host?
  • Where should the 10-second timeout number actually come from?

Suggested practice checklist

Built from the rounds and topics Fox candidates report.

Day 1 — Map the Fox loop

  • Write out the reported sequence: Application Review, Screening Round, Technical Implementation, Evaluation, Final Decision.
  • For each round, write one sentence on what it is judging, from the description above, and mark the one you are least ready for.

Deliverable: A one-page map of the 5 reported rounds, with the weakest marked.

Day 2 — Work Data Structures & Algorithms (DSA)

  • Spend the session on Data Structures & Algorithms (DSA), which Fox candidates report being tested on.
  • Write one worked example in Data Structures & Algorithms (DSA) and time yourself on it.

Deliverable: One timed worked example in Data Structures & Algorithms (DSA).

Day 3 — Work Binary Trees

  • Spend the session on Binary Trees, which Fox candidates report being tested on.
  • Write one worked example in Binary Trees and time yourself on it.

Deliverable: One timed worked example in Binary Trees.

Day 4 — Work Tree Construction from Level Order Traversal

  • Spend the session on Tree Construction from Level Order Traversal, which Fox candidates report being tested on.
  • Write one worked example in Tree Construction from Level Order Traversal and time yourself on it.

Deliverable: One timed worked example in Tree Construction from Level Order Traversal.

Day 5 — Answer out loud: Data Structures and Algorithms

  • Answer aloud, timed: Given the level order of a binary tree, construct a binary tree.
  • Answer aloud, timed: Validate a Binary Search Tree (BST).

Deliverable: Spoken answers to 2 reported Data Structures and Algorithms question(s), under time.

Day 6 — Rehearse your own examples

  • Prepare three examples from your own work where you made the decision, each with the outcome you can quantify.

Deliverable: Three examples written out, each with a number attached.

Day 7 — Dry run for Fox

  • Run one full mock under time, then write down the two questions you most want to ask your interviewers.

Deliverable: A completed timed mock and two questions to ask.

Behavioural preparation

Behavioural rounds judge the decision you made and what it cost.

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?

Tell callers you do not own that their integration breaks

Medium · deprecation · compatibility · stakeholders

A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.

Approach

  1. Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
  2. Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
  3. Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
  4. Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.

Follow-up

  • How would you detect a consumer that reads the field only during a monthly export?
  • One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?

Ship under a deadline and bound the debt you chose

Medium · pagination · technical debt · tradeoffs

You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.

Approach

  1. Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
  2. Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
  3. Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
  4. Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.

Follow-up

  • At what page depth does the offset version breach your latency budget, given your page size and row counts?
  • What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?