Flexport combines software, data analytics and physical infrastructure for shipping goods by ocean, air and land. The source notes for this role describe engineering work tied to physical supply chains: real-time cargo tracking aggregators, container yard allocation, customs compliance automation, and transaction engines for freight buyers and sellers. Depending on the team, the languages named are Ruby, Java, Go, TypeScript and React.
Day to day, the notes describe engineers working with product managers, operations domain experts and UX designers to turn supply chain workflows into software, plus code reviews, telemetry monitoring, fixing operational bottlenecks and modernizing legacy workflows. When you prepare examples, choose ones where you modeled a messy real-world process in code and kept it running in production.
For the interviews, the reported questions lean toward practical problems over puzzle-style algorithms. Coding prompts model domain entities such as containers, orders, cards or freight rates and then add requirements in phases. The source notes say candidates are evaluated on how they structure code for long-term maintainability, not only on whether it passes tests. Algorithm questions cluster around grids, intervals, heaps and backtracking. Design questions center on reservations with concurrent bookings and on ingesting high volumes of location updates. Divide your preparation across all four categories and confirm the current format with your recruiter.
Preparation focus
editorialPracHub has not confirmed a round sequence for Flexport. The source notes describe a recruiter screen or online assessment, then a technical phone screen with live coding or domain modeling in a shared editor such as CoderPad, then a virtual onsite that includes practical object-oriented design problems, a system design scenario based on operational workflows, a technical project deep dive and a behavioral interview with an Engineering Manager. Use those stages as preparation areas, and ask your recruiter which ones apply to your loop.
What to demonstrate
- Turning a vague business prompt into domain classes with clear responsibilities and state transitions, then extending them as new phases arrive
- Core algorithm fundamentals on grids, graphs, intervals and heaps, with stated time and space complexity
- System design for reservations and tracking: API contracts, schemas, and handling concurrent writes
- Explaining a past project's architecture and trade-offs, plus ownership and collaboration in behavioral answers
How to prepare
- Solve each reported OOD prompt in phases: model the entities first, then add one requirement at a time without rewriting earlier code
- Drill BFS and flood fill on grids, meeting-room style interval counting, and a heap you write yourself
- Design one reservation system and one GPS tracking pipeline end to end, naming how double bookings are prevented
- Rehearse one project deep dive with a block diagram, the main trade-offs and what you would change today
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
Flexport Software Engineer Interview Experience — Onsite Word-Guessing Algorithm Question, No Offer
This year, all the onsite questions were new ones -- job hunting is really tough this year. Hoping this helps other people who are also job hunting. Phone screen: an algorithm question. Given a bunch of meeting start and end times, output the total time spent, counting any overlaps only once. Follow-up: if there's overlap, you'd need multiple meeting rooms -- find the minimum number of meeting ro…
Read full experienceFlexport Software Engineer Interview Experience — Rejected Half an Hour After a Screen With Broken Example Answers
View report detailsPracHub editorial advice for the preparation topics above.
Jumping straight to a memorized algorithm pattern on a practical OOD prompt before defining any domain classes
On prompts like the container yard or card-purchasing system, start by naming the entities (Container, Yard, ClientAccount, Order; or Card, Player, TokenWallet), what state each owns, and the public methods the prompt needs. Say this out loud before you write code. Then implement the smallest version that runs. The source notes warn against skipping the domain model and report that code organization and extensible design are graded heavily. A clean model also pays off directly, because each later phase builds on it.
Writing one long function that works for phase one and has to be rewritten when phase two adds discounts, balances or a third dimension
Keep separate concerns in separate places: pricing rules separate from currency conversion in a rate calculator, win detection separate from board storage in a Connect-N engine, balance changes separate from order matching in a yard system. When a new requirement arrives, add a class or strategy rather than branching inside existing code. After each phase, spend a moment pointing out what you would refactor and which edge cases remain.
Designing a reservation or booking system without saying how two concurrent requests for the same slot are kept from both succeeding
Name the mechanism and where it lives: a unique constraint on (resource, slot), a version-checked conditional UPDATE, or a row lock held from the availability check through the insert. Explain what the losing request sees and whether it retries. Reading availability and then inserting without a guard is the bug to rule out. Then state the trade-off you are making, for example optimistic versus pessimistic locking, instead of leaving it for the interviewer to raise.
Re-running a full BFS for every query in the multi-query grid path problem
When a grid gets many 'is there a path from A to B' queries and the grid does not change between them, label connected components once with BFS or union-find in O(rows × cols), then answer each query by comparing component labels in O(1). Ask early whether the grid can change between queries, because that decides whether precomputing is valid. Do the same for flood fill: traverse from the boundary cells once, rather than testing each island separately.
Walking through a past project as a list of features, with no diagram, data flow or rejected alternatives
Pick a project where you made the key architectural calls. Prepare a block diagram you can redraw quickly, the path one request or record takes through it, the two or three decisions you would defend (and the options you rejected), and what you would change today. The source notes describe defending technical choices under detailed questioning, so know the numbers and failure modes of your own system.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Implement a card and token purchasing system where players can check e…
Implement a card and token purchasing system where players can check eligibility, purchase cards with points, and track card-color discounts.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Design a container yard management system that tracks container invent…
Design a container yard management system that tracks container inventories, client balances, and buyer/seller order book fulfillment.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Create a freight rate calculator supporting fixed pricing tiers, volum…
Create a freight rate calculator supporting fixed pricing tiers, volume discounts, and variable currency updates.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Build a game state engine for an extended Connect 3 or 3D N-in-a-row g…
Build a game state engine for an extended Connect 3 or 3D N-in-a-row game, validating winning configurations across rows, columns, and diagonals.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
- 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?
- Which test case would catch an off-by-one here?
Parse partner quantity and date segments without floating point
A partner sends a delimited flat file whose segments look like QTY+17:4.500:CS and DTM+232:20260317. Quantities carry up to three decimal places in a named unit; each item has an integer pack factor to eaches that is versioned by effective date. Produce observation_event rows with an integer quantity in eaches, the unit recorded, and occurred_at resolved, rejecting any line whose conversion is not exact. Files reach 4 GB. Single pass, no floating point. State your bounds and what you do with the zoneless date.
Approach
- Scan with indices, not a regex. Find the segment terminator, then split the payload on
:by scanning forward and slicing — O(bytes) with no backtracking and no per-line allocation beyond the output row. A regex with nested quantifiers over 4 GB is where this job silently becomes CPU-bound. - Parse the decimal as an integer mantissa plus a scale.
4.500becomes(mantissa = 4500, scale = 3): accumulate digits into an int64, count digits after the point, and never call the runtime's float parser. Reject a mantissa that would overflow int64 when multiplied by the pack factor, checking before the multiply rather than after. - Convert by integer arithmetic and demand exactness:
numerator = mantissa * factor; emitnumerator / 10^scaleonly whennumerator % 10^scale == 0, otherwise reject the line with a reason code. Worked:4.500 CSat 12 eaches per case gives 54000/1000 = 54, exact.0.125 PLat 36 eaches per pallet gives 4500/1000 = 4.5, which is half a sealed unit and must fail at the boundary, not round three services away. - Select the pack factor by the file's effective date, not by the current item master. Factors are properties of a point in time — a supplier moving a case from 12 to 10 must not reinterpret last quarter's file — so the lookup is a range query on the factor's validity interval.
20260317carries no zone and no time. Attach the IANA zone of the node the segment refers to, captured at ingestion, because it cannot be recovered downstream; then pick and document one convention for a date-only field (start of local day is the usual choice) and store the raw local string beside the resolved instant so the assumption stays visible. Complexity: O(total bytes) time, O(longest line) working space, one pass.
Worked solution 20 min
- Write the integer conversion as a pure function
(mantissa, scale, factor) -> Result<int64>and unit-test it before touching the file reader. - Table-drive the cases: exact conversion, non-dividing conversion, a mantissa near int64 overflow, a zero quantity, and a unit not in the ladder.
- Wire the scanner, emitting one rejection row per bad line with the raw segment attached so the partner can be shown what was sent.
- Feed a fixture file containing a date on a day the destination zone changes offset, and assert the resolved instant against a hand-computed value.
- Re-run the whole file and confirm identical output, including identical rejections.
Follow-up
- The same file is re-sent in full tomorrow with three lines changed. What makes the second ingest a no-op for the unchanged lines?
- A line names a unit that is not in the ladder for that item. Reject the line, reject the file, or quarantine it — and who finds out?
- The partner starts sending an offset like
+0100on some dates. Does that change your storage decision for a future appointment as well as a past event?
Fold late, out-of-order scan events into one status per leg
observation_event holds event_id, subject_type, subject_id, observed_status, occurred_at, received_at, dedupe_key (UNIQUE) and processing_status. A lookup table maps observed_status to status_rank SMALLINT along a progress lattice. Write one query returning the current status of every shipment leg that received an event in the last hour, such that a picked_up event arriving after a delivered event never changes the answer. Then explain exactly which part of your ordering makes a shuffled replay of the same batch produce identical output.
Approach
- Separate the two jobs. Deduplication is the UNIQUE on dedupe_key at ingest, because partner feeds resend whole windows; ordering is the fold. Either alone leaves a defect: dedupe without a lattice still lets a stale event win, and a lattice without dedupe double-counts a replayed window in anything that aggregates.
- Select the affected legs in one CTE by received_at >= now() - interval '1 hour', then fold over every event for those legs, not only the recent ones. Restricting the fold's input to the recent window is the failure: a lone late picked_up would be the only row considered and the leg would regress.
- Fold as a maximum over the lattice: SELECT DISTINCT ON (subject_id) subject_id, observed_status FROM ... ORDER BY subject_id, status_rank DESC, occurred_at DESC, event_id DESC. The equivalent form is ROW_NUMBER() OVER (PARTITION BY subject_id ORDER BY status_rank DESC, occurred_at DESC, event_id DESC) = 1.
- The determinism comes from the final event_id tiebreak. Rank and occurred_at alone leave two equal-rank events at the same instant to be resolved by heap order, which changes when rows are re-inserted, so the shuffled replay differs in exactly the cases nobody tests. With a total order over the tuple the fold is a max, which is commutative and associative and therefore order-insensitive.
- Do not advance a leg on occurred_at alone. The timestamp is device-reported and can be wrong, which is why the lattice rank leads the ORDER BY and why clock_offset_ms is worth storing: a future-dated event with a low rank then cannot promote itself past a delivered.
- Keep superseded rows and mark processing_status = 'superseded' rather than deleting them. The discarded event is usually the one that explains a delivery dispute, and deleting it makes the trace unanswerable.
Follow-up
- An event arrives a week late for a leg that is already invoiced. Does the fold apply it, reject it as late, or apply it and emit a correction?
- The stream is partitioned by source_id rather than by subject_id. What ordering guarantee do you actually have, and what breaks?
- How would you materialise this fold so the customer-facing status read is not running a window function per request?
Write the reconciliation query that finds ledger and position variance
inventory_movement(movement_id, item_id, node_id, lot_id, state, delta_qty) is the ledger; inventory_position(item_id, node_id, lot_id, state, qty, version, last_movement_id, reconciled_through_movement_id) is its running sum, written in the same transaction as each movement. Write one query that reports every key whose stored qty disagrees with the sum of its movements, including keys present in only one of the two tables, bounded by a watermark movement_id you pass in. State what the job does with each variance, and how you avoid re-summing four billion rows every night.
Approach
- Aggregate once and join once: a CTE doing SELECT item_id, node_id, lot_id, state, SUM(delta_qty) AS ledger_qty FROM inventory_movement WHERE movement_id <= :w GROUP BY 1,2,3,4, then FULL OUTER JOIN inventory_position USING those four columns. One pass plus one hash aggregate, O(n) in movements scanned; a correlated subquery per position row instead costs one index scan per key and is orders of magnitude slower at 40 million keys.
- Use FULL OUTER JOIN, not LEFT. A key with movements and no position row is the signature of a lost position update, and a position row with no movements is the signature of a write that bypassed the ledger. Both disappear under a LEFT JOIN from either side.
- Compare with IS DISTINCT FROM and COALESCE the join keys out of both sides, so a missing row reads as a variance rather than as NULL = NULL evaluating to unknown and being filtered away.
- Run both sides in one snapshot: either a single statement, or a REPEATABLE READ transaction. Reading positions in one transaction and movements in another manufactures variances for every key written in between.
- Make it incremental with reconciled_through_movement_id: sum only movements in (reconciled_through, :w] and add that delta to the previously verified total. The catch is that sequence values are handed out before commit, so a movement whose id is below :w can become visible after the scan and would then never be re-summed. Set :w to a max(movement_id) observed a few minutes earlier, beyond the longest write transaction, and full-scan one partition per night on a rotation as a backstop.
- A variance is reported, not repaired. Rewriting qty to match the ledger hides the double-applied retry that caused it; only a physical count may produce a cycle_count_adjust movement, and the location should be flagged unreliable to planning until it is counted.
Worked solution 25 min
- Write the aggregate CTE with the watermark predicate, then the FULL OUTER JOIN and the IS DISTINCT FROM filter, projecting COALESCE(m.item_id, p.item_id) and both quantities.
- Seed a fixture with four cases: agreeing key, disagreeing key, movements with no position row, position row with no movements.
- Run it and confirm exactly three rows come back, with the agreeing key absent.
- Rewrite the same comparison as an incremental sum from reconciled_through_movement_id and confirm it reports the same three keys on a second run after new movements land.
Follow-up
- The job finds 14 variances, all on one node, all negative. What do you look at first, and what would make you suspect the reconciliation job itself rather than the data?
- How do you run this without the nightly scan competing with the ATP read path for buffer cache and I/O?
- What does the query return for a key whose only movements are a receipt and its exact reversal, and is that row worth reporting?
Allocate stock on hot item-node pairs without overselling
Allocation peaks at 3,000 writes per second and 70 percent of it lands on about 50 (item, node) pairs during a promotion, roughly 42 per second against a single position row. The measured allocate transaction is 4 ms. Design the call so the availability check and the allocation insert cannot interleave: name the mechanism, state its throughput ceiling on one row, and say what the losing caller sees. Then say what changes when one order line must claim quantity at two nodes atomically.
Approach
- Put the check and the write in one atomic step, in one of two shapes. Pessimistic: SELECT the position FOR UPDATE, verify on_hand minus outstanding held and committed allocations covers the request, insert the allocation and bump version. Optimistic: UPDATE inventory_position ... WHERE key = ... AND version = ? AND qty - reserved >= ?, and test the affected-row count. A read followed by an unconditional insert is the defect, and no isolation level repairs it.
- Do the ceiling arithmetic. The row lock is held from the moment it is taken until commit, so a 4 ms transaction supports roughly 250 allocations per second on one row against the 42 demanded, about six times headroom. That headroom is the entire budget, which is why nothing remote may sit inside the transaction: a 200 ms carrier or rating call drops the same row to about 5 per second, eight times under demand, and the pair queues until the pool is exhausted.
- Be exact about isolation rather than waving at it. Under PostgreSQL REPEATABLE READ a second writer to the same row aborts with a serialization failure the application must catch and retry; under InnoDB REPEATABLE READ a locking read sees the latest committed row and blocks instead, so identical code is safe on one engine and not the other. READ COMMITTED with a locking read or a version-predicated UPDATE is the portable form.
- Define the loser's experience. A serialization failure or a zero-rowcount CAS is an expected outcome, not an error to log and swallow: retry with jittered backoff a bounded number of times, then return a typed insufficiency so sourcing picks another node. Uncapped retries against one hot row are the real outage, so cap attempts and shed.
- Shed without overselling by making the hot key single-writer: route allocations through a log partitioned on hash(item_id, node_id) so one process touches the row and contention becomes queueing instead of lock waiting. The costs are head-of-line blocking per key and duplicate delivery on rebalance, so the consumer still needs the partial unique index on allocation to make a replay fail closed rather than double-claim.
- For two nodes atomically: inside one database it is one transaction taking both position rows in a deterministic order, ascending by primary key, so two lines claiming the same pair cannot deadlock. Across shards no such transaction exists, so it becomes a saga - hold at A, hold at B, release A by a compensating status change on failure - and the pair is briefly held but uncommitted, which is a state the order line's machine must represent.
Follow-up
- Two lines claim the same two nodes in opposite order and the engine kills one. What is the lock ordering rule, and what does the survivor's retry look like?
- Soft allocations expire. What does the sweeper do, and why is it a correctness component rather than housekeeping?
- Demand on one pair reaches 400 per second. Which of the two mechanisms do you move to, and what do you give up?
Gateway that books carriers at most once through timeouts
The Carrier Integration Gateway buys bookings and labels from 30 partners. Peak is 300 booking calls per second, partner p99 is 3 seconds, and stalls of several minutes occur. Each partner admits roughly 10 requests per second and rejects the rest. A booking is physically irreversible: a duplicate is a second trailer and a second invoice. Design the call path and the retry path so one shipment leg is booked at most once, including what is recorded before the call and how an ambiguous timeout is resolved.
Approach
- Generate booking_idempotency_key and persist it on shipment_leg with status requested before the socket is opened. The durable row is the memory, not the process, which is what makes a crash mid-call recoverable. Send the key to the partner where their contract supports one, but never depend on their deduplication: their window is short, their key is often derived from fields you may legitimately change, and older integrations have no such concept.
- Add an explicit unknown status between requested and confirmed, entered on any timeout, connection reset or 5xx. Nothing on the request path may re-issue from unknown. A sweeper reads rows in unknown and asks the partner about that key - a lookup by reference, or the next manifest - and re-issues with the same key only when the partner affirmatively has no such booking.
- Make invocation asynchronous from the caller. The orchestrator commits its saga step and an outbox row in one local transaction; a relay publishes; the gateway consumes. That is at-least-once publication, not exactly-once, so the gateway consumer must itself deduplicate on (leg_id, booking_idempotency_key) - the outbox moves the problem, it does not remove it.
- Size the queues from the numbers. By Little's law, 300 calls per second held 3 seconds is 900 concurrent calls, so connection and worker budgets are per-partner and total 900 plus headroom. A partner receiving 100 per second of demand against a 10 per second limit accumulates 90 per second of backlog: 5,400 queued after a minute, which is a 9-minute drain at their limit. So every partner gets a bounded queue with a token bucket, and overflow sheds by class - rate quotes dropped, tenders deferred against their cut-off, and a tender that will miss its physical departure escalated to an alternate carrier instead of waiting.
- Separate retryable from terminal. A partner rejection - embargoed lane, undeliverable address, expired account - is not a retry candidate; it goes to a dead-letter queue keyed by leg with the partner's raw error preserved, and the orchestrator sees a terminal failure it can re-source rather than a call that quietly never completes.
Worked solution 30 min
- Draw the leg's booking state machine - planned, requested, unknown, confirmed, failed - and label each edge with the exact event that causes it.
- Write what is committed before the call and what after, identify the crash window between them, and show the sweeper resolving it without a distributed transaction.
- Compute in-flight concurrency from Little's law and per-partner backlog growth at the stated limit, then set queue bounds and shed classes from those numbers.
- Work the three ambiguous outcomes - client timeout, reset after the request was sent, 500 after a partner-side commit - and state the sweeper's action for each.
Follow-up
- A partner has no idempotency concept and no lookup by reference. What is left to you, and what do you hand to operations?
- A stall lasts 40 minutes and 60,000 legs are queued. What do you cancel, what do you keep, and how do you avoid a thundering retry the moment the partner recovers?
- Where is the line between the gateway retrying and the orchestrator re-sourcing, and who owns the cut-off decision?
One partner's leg statuses freeze while every other feed advances
Since 06:40 UTC no leg fed by one partner has changed status; every other feed is current. Ingest is healthy and CPU is flat, and observation_event rows from that source are still landing with current received_at and processing_status 'pending'. Consumer lag is growing on exactly one stream partition, whose head row has over 900 attempts and a byte-identical last_error on each one. Give the ordered diagnosis and the change that stops one unapplicable observation from stalling a partition again.
Approach
- Separate the partner not sending from us not applying, because both look identical to a customer. Rows landing with current received_at and processing_status 'pending' prove ingestion is fine and the fold is stuck; if received_at had gone stale instead, this would be a partner or transport incident and nothing in the consumer would be at fault.
- Read the blocked partition at its head, ordered by offset, and classify the error rather than counting it. An identical error string across 900 attempts is deterministic and implicates the payload; a varying set of connection resets, timeouts and 5xx implicates the downstream store. Only the deterministic case is a poison message.
- Reproduce outside the consumer by running the fold over that one event. The usual cause here is an observed_status the partner added that has no rank in the progress lattice, or a subject_id whose leg does not exist yet, so the fold throws before it can write.
- Decide the exit against the ordering contract, which is per subject and not per partition. Many legs hash into one partition, so blocking on one of them is a bug and not a guarantee. Park the row as processing_status 'rejected_malformed' with its offset in a dead-letter view, advance the offset, and block only the affected subject if the event is well-formed but not yet applicable.
- Make the retry policy classify instead of counting. Deserialisation failures, unknown enum values and constraint violations are terminal and must never be retried in place; IO errors and timeouts are retryable with bounded attempts and backoff. Alert on oldest-pending age per partition, which turns positive within seconds, rather than on lag, which drifts up slowly and looks like normal backlog.
Follow-up
- The parked event turns out to be a real delivered scan. How do you replay it after the code fix without double-applying, and what makes that safe?
- What should the customer-facing status show for legs that were queued behind it?
- The partition key is currently the leg id. What breaks if you key by partner instead, and what if you key randomly?
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 done01Practical OOD: model the domain, then grow it in phases
- Take the reported container yard prompt: list entities (Container, Yard or Slot, ClientAccount, Order with a buy or sell side), their state and public methods before any code.
- Implement it in three phases without rewriting earlier code: store and remove containers, then client balances, then matching buy and sell orders. Note every place a phase forced an edit.
- Take the card and token purchasing prompt: model Card (cost per color, points, color), Player (tokens, per-color discounts), and a canPurchase that subtracts discounts from cost with a floor of zero before checking tokens.
- Say your class design out loud before coding each prompt, as you would in the room.
Deliverable: Two phased OOD solutions, plus a list of the edits each new phase forced and how you would design to avoid them.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Practical OOD under changing requirements
- Build the freight rate calculator with pricing tiers, volume discounts and currency conversion as separate components. Store money as integer minor units or a decimal type, never floats.
- Build the Connect-N engine so a move only checks lines through the last piece: count matching pieces in both directions along each axis (4 line directions in 2D, 13 in 3D).
- Extend each solution with one requirement you did not plan for (a new discount rule, a new board dimension) and measure how much existing code you had to change.
- Review each solution out loud: naming, edge cases (empty board, full column, zero volume) and the refactor you would make next.
Deliverable: Two extensible solutions with a short list of the edge cases each handles and the one refactor you would do next.
Practice prompt ↗Practice prompt ↗03Grid, graph and interval algorithms
- Grid path queries: label connected components once with BFS or union-find, then answer each query in O(1). Write out the complexity before and after precomputing.
- Flood fill: mark all land reachable from the boundary cells, then submerge every unmarked island. Test a grid with no boundary land and a grid that is all land.
- Meeting rooms or docking bays: solve with a min-heap of end times and again with sorted start and end arrays. Decide whether an end equal to a start counts as overlap. Pair with the bank question on meeting coverage and required rooms.
- Write a max-heap with sift-up and sift-down, and a weighted dice roller using prefix sums plus binary search.
Deliverable: Four solutions with stated complexity and one test each for the edge case most likely to break them.
Practice prompt ↗Practice prompt ↗04Backtracking and dynamic programming
- Phone number to strings where keys can take multi-digit inputs: solve with backtracking over how the digits split up, then note where memoization pays off.
- Restore IPv4 addresses from a digit string (bank topic): backtrack over the dot positions, reject segments above 255 and segments with leading zeros.
- Practice the classic house-robber DP, first the linear version in O(n) and then the circular version, as preparation for the bank topic on two robbery optimization variants.
- Target-word guessing (bank topic): pick each guess to minimize the size of the worst-case group of remaining candidates, and explain why that bounds the number of guesses.
Deliverable: Four solved problems, each with its recurrence or pruning rule written above the code.
Practice prompt ↗Practice prompt ↗Worked solution ↗05System design: reservations, tracking and assignment
- Design a reservation system in the style of the reported OpenTable-style prompt: endpoints, schema, availability search, and the exact mechanism that prevents double booking. Use the drill on allocating stock on hot item-node pairs to practice the throughput ceiling of a single hot row.
- Design the GPS tracking aggregator for trucks and vessels: ingestion stream partitioned by vehicle, separate latest-position and history stores, and out-of-order handling as in the SQL drill on folding late, out-of-order scan events.
- Work through the carrier gateway worked exercise (booking carriers at most once through timeouts) and run its checks against your own answer.
- Sketch the bank design topic on assigning containers when some goods cannot share a container: the data model, the assignment rule, and what happens when no valid assignment exists.
Deliverable: Two full designs with APIs, schemas and a concurrency story, plus notes on the carrier gateway worked exercise.
Practice prompt ↗Practice prompt ↗06Data modeling, SQL and debugging drills
- Complete the worked SQL exercise on ledger and position reconciliation, including every check it lists.
- Write the event-time fold from the late, out-of-order scan events SQL drill and show that replaying the events in shuffled order gives the same result.
- Complete the worked coding exercise on parsing partner quantity and date segments without floating point, and grep your parser for float types.
- Diagnose the frozen-partner-feed debugging drill out loud in order: prove ingestion is healthy, classify the error, then park the poison message.
Deliverable: Two SQL queries that pass their checks, one integer-only parser, and a written diagnosis sequence for the stalled partition.
Practice prompt ↗Practice prompt ↗07Project deep dive, behavioral and a full mock
- Prepare the project deep dive: a block diagram you can redraw, the path one request takes through it, three defended decisions, and what you would change today.
- Write stories for the reported behavioral themes: leading a project from scratch, a project that went off track, quality versus deadlines, and a technical disagreement.
- Run a mock of one multi-part OOD prompt from days 1 and 2 with a partner who adds a new requirement partway through.
- List the weakest area from the week and redo one problem from it without notes.
Deliverable: A rehearsed deep dive, four behavioral stories, and notes from one mock with a mid-problem requirement change.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The source notes describe a behavioral interview with an Engineering Manager and a technical project deep dive. The themes they name are ownership, mentoring, communicating through ambiguity and tying technical decisions to user needs. For each story, state the decision you made, the alternative you rejected, and the result you measured, and be ready for follow-ups on what you would change.
Unblock an engineer whose replayed feed double-counts
An engineer on your team consumes a partner status feed that resends a rolling 24-hour window every night. Their consumer folds each message into shipment_leg.status by last write wins, keyed by partner message id. Deliveries are flipping back to in transit each morning, and some receipts are counted twice. They have been on it two days and ask for help. Describe a time you unblocked someone: how you diagnosed it together, which of the two independent defects you pointed at, what you deliberately let them find, and how you checked the fix held.
Approach
- Start by getting the two defects named separately, because they are independent and fixing one hides the other: duplicates are a deduplication problem, and statuses regressing is an ordering problem. A consumer can be perfectly deduplicated and still flap.
- Fix the dedupe key first, since it is the cheaper of the two and it is wrong for a stated reason: a partner message id changes on resend, so it identifies the transmission rather than the observation. The stable key is the source plus the device or partner event sequence, enforced as a unique constraint on observation_event.dedupe_key so a replayed window becomes a no-op at the database rather than a judgement in application code.
- Then fix the fold: order by occurred_at, compare status_rank on the progress lattice and take the maximum, so a picked_up event arriving after delivered is recorded but cannot lower the leg. Keep the superseded observation rather than dropping it, because it is usually the row that explains a later dispute.
- Teach the diagnostic rather than the answer. Ask them to replay one leg's observations in shuffled order and assert the same final status, which is the property that makes the fold order-insensitive and is a test they can write in twenty minutes. Let them discover from their own data that occurred_at is sometimes wrong, which is why clock_offset_ms is stored beside it and why the lattice, not the timestamp, is what guarantees monotonicity.
- Check it held with data rather than with a green build: count legs whose status_rank decreased in the last day, expect zero, and keep it as a standing assertion. Say what you did with the rows the old consumer had already corrupted, because leaving them is a decision too.
- Be honest about the handoff. Say how long you spent, what you did not do for them, and whether they could explain the fix back to you afterwards, which is the only durable test that the unblocking worked.
Follow-up
- They ask whether broker ordering guarantees solve this for them. What do you say?
- An event arrives a week late and its status is below the current one. What should the pipeline do with it, and what should it tell anyone reading the leg?
- How do you repair the legs already corrupted, given the observations are all still stored?
Reverse an allocation design after peak contention
You chose optimistic concurrency for allocation: read the position, then UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE version = :v, retrying on an affected-row count of zero. It was correct and fast in load tests with spread keys. At peak, a few hundred hot item-node pairs absorbed most write traffic, retries amplified, and allocation p99 went past the checkout budget. Describe a decision you reversed under production evidence: what you originally reasoned, the measurement that forced the change, what you replaced it with, and what you would have measured before committing.
Approach
- Say why the original choice was reasonable, because a reversal story is only useful if the first decision was defensible. Compare-and-set avoids holding a lock across the read, has no deadlock surface, and is uncontended on the long tail of keys, which is most keys most of the time.
- Name the mechanism of the failure rather than calling it contention. On PostgreSQL under READ COMMITTED, a conflicting UPDATE does not fail fast: it blocks on the row lock until the other transaction commits, then re-evaluates its predicate against the new row version and reports zero rows affected. Each loser therefore pays a full lock wait before learning it must retry, so with k writers queued on one pair the work is quadratic in k across the burst, and the retry loop adds round trips rather than avoiding waits.
- Bring the measurement that settled it, not the anecdote: attempts per successful allocation on the hottest pairs, the distribution of write traffic across item-node keys, and the p99 contribution of lock wait time separated from query time. Load tests with spread keys cannot show any of this, which is the real lesson and the thing you would run differently.
- State the replacement and its cost. Serialising each hot key behind a single writer with a bounded queue converts an unbounded retry storm into a bounded wait plus explicit shedding, at the cost of a new component, a routing decision and a failure mode when the writer for a key is unavailable. SELECT ... FOR UPDATE is the smaller change and trades the retry loop for an in-database queue that still consumes a connection per waiter.
- Describe the migration, since reversing a write path in production is where these stories become concrete: route only the measured hot keys first, keep both paths live behind a per-key decision, and verify with the same attempts-per-success metric before widening.
- Close on what you would have measured before committing, and be specific: key skew from production traffic, not from a synthetic generator, is the input the original decision was missing.
Follow-up
- Under REPEATABLE READ on PostgreSQL that same conflict raises a serialization failure instead. What changes in your retry code and your error budget?
- One key becomes so hot that even the single writer saturates. How do you shed load without overselling?
- An allocation must span two positions atomically. What breaks in your replacement design if those rows live on different shards?
Turn an implicit fail-open availability answer into a decision
When the inventory service is unreachable, the availability client returns the last value it cached, because a broad except block around the call does that by accident. Nobody chose it. During a 40-minute outage the storefront kept promising stock for items that had sold out, and 900 lines short-shipped. Describe a time you converted an implicit behaviour into an explicit decision: how you surfaced it, the two directions the error can go and what each costs, what you actually chose for the checkout path versus a browse page, and where the choice is now written down.
Approach
- Surface the behaviour as a fact before arguing about it: the except block is a policy, it was never chosen, and it currently applies identically to a browse page and to the promise made at checkout. Framing it as an unowned decision rather than as someone's bug is what gets it looked at.
- Price both error directions with the operations they trigger, since neither is free. Promising stock you do not have produces a short-ship, a customer contact, a re-source or refund and, on the floor, a picker sent to an empty location; refusing stock you do have loses the order outright. Both are real, the second is cheaper to reverse, and that asymmetry is the argument.
- Split the decision by path instead of choosing one policy for the system. A browse page can serve a stale value with a staleness label and be right most of the time; a checkout promise should degrade to a conservative answer, which is not zero but the last known position reduced by an in-flight allowance sized from the recent allocation rate for that key. Applying one policy to both paths is the actual defect.
- Bound the staleness, because a cached value with no age is the same bug wearing a different hat. Serve the cached number while it is under a stated age, then switch to the conservative answer, then refuse; the thresholds need owners and should appear on a dashboard as the share of reads answered from each tier.
- Write it down where the next person meets it: a short decision record giving the behaviour, the two costs, the chosen policy per path and the date, plus a test that asserts the checkout client cannot serve an unlabelled stale value. A decision that lives only in a thread gets reverted by the next broad except block.
- Report what the change actually cost. If the conservative answer suppressed some orders that would have been fillable, say how many, because a degradation policy that was never measured is indistinguishable from one that was never enforced.
Follow-up
- How do you size the in-flight allowance, and what happens to it during a promotion on that item?
- The outage is 4 hours rather than 40 minutes. Does your policy still hold, and what changes at which threshold?
- Someone adds a new consumer of the availability client next quarter. What stops them inheriting the wrong policy by default?
- 01
Describe a challenging engineering project where you led the technical implementation from scratch.
- 02
Walk through a situation where a project failed or went off track, and explain what you learned from the experience.
- 03
How do you balance software quality, test coverage, and clean abstraction against urgent business delivery deadlines?
- 04
Describe a time you had a technical disagreement with a teammate or stakeholder and how you resolved it.
- 05
Walk through the architecture of a major system you designed from scratch. What were the biggest trade-offs and what would you change today?
- 06
Tell me about a time you received critical feedback on your code or design and how you incorporated it into your workflow.
Is this an official Flexport interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Flexport. Rounds and questions reflect what candidates have reported, not a process Flexport has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What stages have been reported for the Flexport Software Engineer interview?
The source notes describe a recruiter screen or online assessment, then a technical phone screen with live coding or domain modeling in a shared editor such as CoderPad, then a virtual onsite. The onsite is described as including practical object-oriented design problems, a system design scenario based on operational workflows, a technical project deep dive and a behavioral interview with an Engineering Manager. PracHub has not confirmed a fixed sequence, so ask your recruiter which stages apply to you.
PracHub Software Engineer practice ↗How do Flexport's technical interviews differ from algorithm-heavy loops?
The reported questions lean toward practical object-oriented design and domain modeling: a prompt about containers, orders, cards or freight rates that gains new requirements in phases. Algorithm questions still appear, mostly grids, intervals, heaps and backtracking. Prepare for both. The source notes report that adapting code to changing business requirements is assessed, so on practical prompts show how your class structure copes with the next requirement, not only that the first version works.
PracHub interview research ↗How should I approach a multi-part practical coding problem?
Before writing code, ask about the business rules, edge cases and expected inputs. Then name the entities and their responsibilities, implement the smallest working version, and add each later requirement as a new class or strategy rather than a new branch in an existing function. After each phase, briefly review your code: what you would refactor and which edge cases you have not covered.
PracHub Software Engineer practice ↗What should I focus on for system design?
The reported design prompts include a reservation system with concurrent seat locks, a high-throughput GPS tracking aggregator for trucks and vessels, a carrier booking system that picks the lowest-price routes, and an API and schema for a multi-node logistics network with warehouse inventory. Practice defining endpoints and schemas, stating how double bookings are prevented, and explaining how out-of-order location updates are handled. Say the trade-off out loud whenever you choose between options such as SQL and NoSQL or optimistic and pessimistic locking.
PracHub interview research ↗Can I complete coding rounds in any programming language?
The source notes say you can generally use any major language, such as Java, Python, C++, Go or JavaScript/TypeScript. Because practical OOD problems are common, pick a language where you can write classes, interfaces and tests quickly and cleanly.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24