A Software Engineer at Porter Airlines Inc. plays a pivotal role in maintaining the digital infrastructure that keeps a modern, growing airline operational. Whether working on development and delivery or cloud operations, your work directly influences the passenger experience, booking reliability, and the efficiency of internal airline systems. You are not just writing code; you are building the digital backbone that supports the company’s commitment to providing a refined, elevated travel experience.
This role requires a blend of technical precision and the ability to navigate complex, time-sensitive business environments. You will often collaborate across departments to solve unique challenges, such as integrating real-time flight data or managing scalable cloud environments. It is a position that demands both high-level system thinking and the granular attention to detail required to keep mission-critical airline software running smoothly.
Initial Screening
reportedThe title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.
What to demonstrate
- Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
- Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
- Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year
How to prepare
- Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
- Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
- Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter
Technical Assessments
reportedThe same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Behavioral Rounds
reportedYour first answer is not really what is scored. It buys the follow-up questions, and those decide the round. An interviewer with fifteen minutes takes one thread and pushes on it four or five times, so a story you can only tell at a single level of detail collapses under the third why. That is an argument for fewer stories known deeply rather than one prepared per prompt. Four or five pieces of work you can still explain down to the code you changed and the argument you had about it will cover nearly anything asked in this round.
What to demonstrate
- Whether a story holds as the questioning moves from what you did to why that instead of the alternative, and then to what you would change knowing what you know now
- Whether you can re-cut a project to answer the question actually asked rather than delivering a rehearsed block that answers an adjacent one
- Whether your level of detail is chosen rather than habitual: going down to the schema when the question is about the data model, staying out of it when the question is about the person who disagreed with you
How to prepare
- Pick four projects and write the chain out four levels deep for each: what you did, why that, why not the alternative, and what would have to be true for the alternative to have won. Where you cannot reach the fourth level, you have a placeholder rather than a story
- Have someone ask why three times in a row on a single thread with nothing else added, and mark the point where you start repeating a sentence you already said. That point is where the interviewer stops learning anything
- Build a one-page index instead of an answer bank: the common prompts in this round (disagreement, a failure that was yours, thin requirements, a deadline you missed, work you inherited) mapped to which of your four projects you would use for each, so the choosing is done now rather than while an interviewer waits
PracHub editorial advice for the preparation topics above.
Waiting on every supplier in the fan-out
If N connectors are independent and each exceeds its own 99th-percentile latency one percent of the time, the probability that at least one of them does is 1 minus 0.99^N, which is roughly 18 percent at N equals 20. A request that waits for all of them therefore inherits a tail that no amount of optimising any single connector will fix, and the usual instinct of raising the timeout makes it strictly worse. The workable design is a total deadline budget for the request, per-call timeouts derived from the budget remaining at the moment the call is issued rather than fixed constants, partial results returned with an explicit completeness flag, and a circuit breaker so a supplier that is already down stops consuming budget on every subsequent request. The independence assumption is itself worth checking, because shared infrastructure makes connector latencies correlate exactly when you need them not to.
Modelling availability as a boolean
Bookability is a count plus a set of per-night restrictions whose scopes differ, and collapsing that into is_available breaks in both directions at once. Closed-to-arrival applies only to the arrival night; closed-to-departure applies only to the checkout date, which is not itself a booked night; stop-sell applies to every night in the range; minimum length of stay is conventionally read from the arrival night's row, though implementations genuinely differ and that is the first thing to pin down with a supplier. A boolean drops stays that are legal (a range whose middle night is closed to arrival is perfectly bookable) and sells stays that are not, and the second kind does not fail in search, it fails at supplier confirm after the traveller has been charged.
Assuming the input fits in memory
Ask how large the input is in bytes before committing to an in-memory algorithm; beyond that point the options are a single streaming pass, an external sort with bounded buffers, or a sketch that trades exactness for constant memory. An algorithm that assumes random access to the whole input is a different algorithm from one that sees each element once.
Finishing a solution without stating its complexity
Give time and space in the same breath as the code, and define n explicitly when there are two sizes, since n nodes and m edges are not interchangeable. Space is the half that gets skipped: count the auxiliary structures you allocate and the recursion stack at its deepest, not only the answer you hand back.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Order saga compensations over the steps that actually ran
A booking saga is a DAG of at most 64 steps with at most 200 edges. Each step carries a compensating action, and some are flagged not cleanly compensable. At runtime every step is in one of three states: completed, unknown (the call timed out and the outcome is not known), or not_started. Produce a valid forward execution order, detect a cycle in the step graph, and on failure produce the order in which compensations must run over the steps that actually ran. Target O(V + E). State explicitly what your compensation order does with an unknown step.
Approach
- Kahn's algorithm for the forward order: compute in-degrees, seed a queue with the zero-in-degree steps, pop and decrement. O(V + E), and with V <= 64 an in-degree array plus an adjacency list is the whole data structure.
- Cycle detection falls out of the same pass. If fewer than V nodes are emitted, the residual nodes lie on or downstream of a cycle — report that set rather than a boolean, because the residual is the diagnosis.
- For compensation, build the induced subgraph over the steps in
completedorunknown, topologically sort that subgraph, and reverse it. Reversing the planned order instead is the bug: it schedules compensations for steps that never ran, and a bareremaining_units = remaining_units + nrelease applied against a hold that was never taken hands out capacity that does not exist. - An
unknownstep is compensated, never skipped. Its compensation begins with a read-back against the stored idempotency key — resolve what actually happened, then cancel it if it happened. A design that treats a timeout as a failure and skips the compensation is how a supplier reservation survives a booking that failed. - Surface the not-cleanly-compensable step at design time rather than at runtime. A supplier confirm against an API with no read-back cannot be resolved automatically, which forces a manual reconciliation queue and changes the operational scope of the feature, so it is a thing to raise in the first five minutes.
- Trade-off: reverse topological order is a partial order, so independent compensations can run concurrently and shorten the window in which money is held without inventory. The price is that a partial compensation failure leaves a mixed state, so each compensation still has to be individually idempotent before you are allowed to parallelise.
Follow-up
- Two compensations are independent in the DAG but contend on the same unit-date rows. Does the partial order still hold, and what do you add?
- A compensation itself times out. What state does that step enter, and how does the reconciler tell it apart from one that never started?
- Where does the transactional outbox sit in this graph, and what does a duplicate delivery do to each consumer?
Merge k sorted supplier streams under a deadline budget
A search fan-out returns K <= 40 supplier result streams, each already sorted ascending by total_minor converted to the display currency using the quote's pinned fx_rate_id. Streams are iterators and advancing one costs a network read. Up to 10,000 results exist across all streams; the page needs the k = 30 cheapest. The request carries a remaining deadline budget, and a stream that misses it is dropped. Return the k cheapest offers plus a completeness flag, minimising elements read. State the cost in heap operations and in stream reads, and state what the flag guarantees.
Approach
- Seed a min-heap with one head element per stream: K entries, heapified in O(K). Do not materialise all 10,000 results — a full merge is O(N log K) with N = 10^4 to produce 30 answers, which is more than two orders of magnitude of wasted network reads.
- Pop the minimum, emit it, then advance only the stream it came from and push its next element. After k pops you hold the k globally cheapest. Elements read is bounded by K + k - 1 = 69, and heap work is k pops at log K, roughly 30 x 5.3 comparisons. Skipping the refill on the final pop is what saves the last read.
- Derive each stream read's timeout from the budget remaining at the instant the read is issued, not from a fixed constant. A fixed per-call timeout lets the last connector spend a budget that earlier connectors already consumed, and the request blows through its deadline while every individual call looks compliant.
- When a stream is dropped, remove it from the heap and set the completeness flag. State the guarantee precisely: the k-th price returned is an upper bound on the true k-th price, because a dropped stream could only have contributed something cheaper. That is a claim a caller can act on; a bare boolean flag is not.
- The sortedness precondition is load-bearing. If the connector contract does not promise it, the shape changes to a bounded max-heap of size k over every result, O(N log k) with no early exit and every element read. Which regime applies is a property of the connector, worth pinning before writing either version.
- Comparison must happen on one currency at one pinned FX rate. Comparing
price_minoracross currencies orders offers by the accident of their ISO 4217 exponent, and re-fetching a live rate mid-merge makes the ordering non-reproducible in a later dispute.
Follow-up
- One stream is consistently slowest but also consistently cheapest. How do you stop the deadline from systematically excluding the best prices?
- Results within a stream are sorted by price, but the page ranks by a blended score. What breaks, and what do you need from the connector to fix it?
- Model the tail: if each of 20 connectors independently exceeds its own p99 one percent of the time, what fraction of requests loses at least one, and does that number justify partial results?
Apply a full refresh as a set difference over the horizon
A supplier sends a full refresh for unit_type_id = U covering the inclusive horizon [H0, H1], at most 500 dates, carrying one supplier_seq for the whole message. You hold stored ari_daily rows for U, each with its own supplier_seq. Classify every date in the horizon into upsert, withdraw, or skipped-by-guard: a date present in the refresh is upserted only if the message seq exceeds that row's stored seq; a date absent from the refresh is withdrawn only under the same condition. Reject refresh rows outside the horizon and report them. Target O(n + m).
Approach
- Walk both sides with two pointers in
stay_dateorder — they usually arrive sorted, and if not, sorting 500 dates is negligible. This gives O(n + m) time and O(1) working space beyond the output, and it preserves date order in the output so the horizon can be streamed. A hash map of stored rows is the same asymptotic time but costs O(n) space and loses that ordering. - Each pointer step resolves into exactly one of three cases. Date in both sides: upsert if
message_seq > stored_seq, otherwise record skipped-by-guard. Date only in the refresh and inside the horizon: insert. Date only in storage and inside the horizon: withdraw ifmessage_seq > stored_seq, otherwise skip. - Assert the accounting as part of the algorithm, not as an afterthought: every date in
[H0, H1]lands in exactly one of upsert, withdraw or skipped, and the three counts sum to the horizon length. That identity catches a boundary error at H0 or H1 and catches a date processed twice, both of which are otherwise invisible until a guest is turned away. - Reject refresh rows outside
[H0, H1]and report them rather than applying them. Applying them means the message's implied withdrawal set no longer matches the horizon it declared, and the next refresh will disagree with this one about which dates it owns. - Keep the guard per row even though the seq is per message. A date whose stored seq already exceeds the message seq has been superseded by a later delta, and treating the message as newer than everything clobbers it and re-opens whatever that delta closed. This is the one place where the set-difference framing and the ordering guarantee interact, and it is where implementations get it wrong.
- Express a withdrawal as
remaining_units = 0withstop_sell = true, not as a row delete. Deleting discards the seq, so the next out-of-order delta for that date has nothing to compare against and applies unconditionally, which reopens inventory the supplier withdrew.
Worked solution 35 min
- Set the horizon to 2026-04-01 through 2026-04-06 inclusive, six dates, with a message seq of 500.
- Stored rows for U: 04-01 seq 410, 04-02 seq 620, 04-03 seq 400, 04-04 seq 430, 04-05 seq 380. No stored row for 04-06.
- Refresh contents: 04-01, 04-03, 04-05, 04-06.
- Walk the two sorted date streams with two pointers and classify each of the six horizon dates, recording the reason for each.
- Check the accounting identity, then re-run the same fixture as a plain row-by-row upsert and note which date comes out different.
Follow-up
- The refresh is delivered twice. Show that the second delivery produces no writes, and name the property that makes that true.
- The message is split across three chunks that can arrive in any order. What has to change about the horizon and the accounting assertion?
- The search path is reading this unit type's range while the refresh is being applied. What can a reader observe mid-apply, and does that matter more or less than the apply's duration?
Reconstruct a booking's state from a redelivered event log
booking_event(event_id BIGSERIAL, booking_id, source, external_event_id, event_type, payload JSONB, occurred_at_utc, received_at_utc) is fed by supplier and processor webhooks that are delivered at least once and arrive out of order. Write one query returning, for each booking in a given set, the state in force at a given instant, how long it had been in that state, and a flag for any event that arrived after a later-occurring one. Deduplicate redeliveries, break ties deterministically, and name the index the query needs.
Approach
- Put the deduplication in the write path where it belongs: UNIQUE (source, external_event_id) with an ON CONFLICT DO NOTHING insert, so a redelivery never lands and the read query carries no correction for a problem the schema can refuse outright. Deduplicating on (booking_id, event_type, occurred_at_utc) instead merges two genuine amendments made in the same second.
- Order by what actually orders. occurred_at_utc first, event_id as the final tiebreak, and say out loud that event_id only orders events from one source, since two suppliers' identifiers mean nothing to each other. A genuinely unordered pair is a real state that should surface rather than resolve to whichever row the executor happened to return first.
- Pick the latest event at or before the instant, per booking. DISTINCT ON (booking_id) with ORDER BY booking_id, occurred_at_utc DESC, event_id DESC is the cheap PostgreSQL form; the portable form is ROW_NUMBER() OVER (PARTITION BY booking_id ORDER BY occurred_at_utc DESC, event_id DESC) wrapped in a subquery filtered to rank one, because a window function cannot appear in WHERE or HAVING — both are evaluated before windows are.
- Compute dwell with LEAD over the same partition in ascending order, subtracting the row's own instant from the next event's. Bound the final row with the as-of instant rather than with now(), or the same historical question returns a different answer every time it is asked.
- Flag arrival disorder with a second window over a different ordering: the running maximum of occurred_at_utc over rows ordered by received_at_utc, excluding the current row, exceeding this row's occurred_at_utc means the event happened before something already processed. That is the case a naive last-write-wins consumer applies backwards, re-opening a cancelled booking.
- Index for the access shape: (booking_id, occurred_at_utc DESC, event_id DESC) turns the per-booking pick into a short index scan, while the unique index on (source, external_event_id) already serves ingest. Without the first, every as-of query costs the size of the booking's partition, so the query gets slower as the log grows rather than as the booking does.
Follow-up
- A cancel event is redelivered after an amendment has re-confirmed the booking. What does your query return, and what should the consumer have done?
- Two sources disagree about the same booking at the same instant. How does the schema record the disagreement instead of silently resolving it?
- The log reaches four billion rows. What does the as-of query cost then, and what would you partition on?
Add a held-units column and constraint to a live calendar
ari_daily holds about 225 million rows, 500,000 unit types over a 450-day horizon, and is upserted continuously by the ingest path while the search path reads it. You must add held_units SMALLINT NOT NULL DEFAULT 0, a CHECK that remaining_units - held_units >= 0, a unique index the hold path needs, and a backfill of held_units from inventory_hold_night, with no window of unavailability. Give the ordered statement plan, the lock each step takes, and how you stop one long-running query from turning a millisecond DDL into an outage.
Approach
- Order the steps so each is cheap in isolation. Adding a column with a constant default has been a catalogue-only change since PostgreSQL 11 and does not rewrite 225 million rows, while a volatile default such as now() still does — so the constant is the whole trick. Add the column, deploy the code that maintains it, backfill, and only then validate the constraint.
- Add the CHECK as NOT VALID first. That takes a brief ACCESS EXCLUSIVE lock, skips the scan and begins enforcing new writes immediately; VALIDATE CONSTRAINT afterwards takes only SHARE UPDATE EXCLUSIVE and scans without blocking readers or writers. Adding it valid in one step instead scans every row while holding the table exclusively.
- Never add the unique constraint directly, because that builds the index under ACCESS EXCLUSIVE. Use CREATE UNIQUE INDEX CONCURRENTLY, which cannot run inside a transaction block, makes two passes over the table and can leave an invalid index behind if it fails — which must be dropped concurrently and retried rather than ignored. Then adopt it with ALTER TABLE ... ADD CONSTRAINT ... UNIQUE USING INDEX, which takes a brief exclusive lock and no rebuild.
- Defend every exclusive moment with a lock timeout and a retry loop. A pending ACCESS EXCLUSIVE request queues behind one long-running SELECT, and every later lock request queues behind that, so a reporting query or a session idle in transaction converts a millisecond ALTER into a full stall on the hottest table you own. Set lock_timeout to something like 100ms, retry with backoff, and inspect the activity view for the transaction you are about to queue behind.
- Backfill in bounded batches keyed on the primary key, committing each one. A single UPDATE across 225 million rows writes a new tuple version per row, roughly doubling table size before autovacuum catches up, holds one transaction open for hours, and blocks the ingest path on every row it has already touched. Batch by unit_type_id ranges, pause between batches, and watch dead tuple counts and replication lag rather than wall-clock progress.
- Account for what the column costs per row. New columns are appended and aligned, so a smallint can cost more than two bytes depending on what precedes it, and because every upsert already rewrites the whole tuple, that width is paid on every write to the table from then on rather than once at migration time.
Worked solution 40 min
- Clone a representative slice, add the column with a constant default, and confirm from timing and table size that no rewrite occurred.
- Add the CHECK as NOT VALID, insert a violating row to prove it is enforcing, then validate it and observe the lock level taken.
- Start a long SELECT in one session and run the ALTER in another with and without lock_timeout, watching the queue build behind it.
- Run the backfill in batches while the ingest path is writing, recording dead tuples and replication lag per batch.
Follow-up
- The concurrent index build fails at eighty percent. What is on disk, what does the planner do with it, and what is your next statement?
- A physical replica serves part of the search traffic. What does replaying this migration do to queries running there?
- How do you make the backfill resumable after it is killed at batch four thousand of five thousand, without rescanning what it already did?
Explain the core principles of Object Oriented Programming.
Explain the core principles of Object Oriented Programming.
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
What is your opinion on the integration of AI in modern software devel…
What is your opinion on the integration of AI in modern software development?
Approach
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
How would you approach building a Weather App from scratch?
How would you approach building a Weather App from scratch?
Approach
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
- 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 three nights of the last unit without overselling
Design the hold service. Input: unit_type_id, check_in, check_out, units. It must hold this invariant: for every (unit_type, stay_date), confirmed booking nights plus active holds never exceed physical_unit_count + oversell_allowance. Tables are ari_daily(unit_type_id, stay_date, remaining_units) and inventory_hold_night(hold_id, stay_date, unit_type_id, units, state, expires_at_utc). Two travellers request the same last unit over overlapping three-night ranges in the same millisecond. Give the exact statements, the lock order, the isolation level you assume and what that engine does to your statement under it, and how hold expiry is evaluated.
Approach
- One conditional UPDATE per night, never a read followed by a write: UPDATE ari_daily SET remaining_units = remaining_units - :units WHERE unit_type_id = :u AND stay_date = :d AND remaining_units >= :units. Inspect the row count of every statement before commit; a zero means that night lost and the whole transaction rolls back. The CHECK (remaining_units >= 0) catches a bug in this logic, it is not the concurrency control.
- Issue the nights in ascending stay_date order inside one transaction and insert all inventory_hold_night rows in the same transaction, so a partially written hold is impossible. Locking in request order instead is where two overlapping multi-night holds take the same row locks in opposite directions and deadlock; PostgreSQL detects that after deadlock_timeout (1s by default) and aborts one side with SQLSTATE 40P01, which shows up as a latency cliff rather than as wrong data, and one second is most of a checkout budget.
- Name the isolation level and its consequence in that engine. Under PostgreSQL READ COMMITTED, a blocked UPDATE re-reads the newly committed row version and re-evaluates its WHERE clause, so remaining_units >= :units is tested against the committed value and the guard holds. Under REPEATABLE READ or SERIALIZABLE the same statement instead raises a serialisation failure (SQLSTATE 40001) that the caller must catch and retry with a bounded budget. Choose READ COMMITTED here and say why: the guard lives in the statement, not in a snapshot read earlier in the transaction.
- Decide expiry deliberately, because with decrement-on-hold the units are only genuinely back when an increment runs. Sweeper-only is simple and understates availability for up to one sweep interval, which is a lost sale on exactly the dates that sell out. Clock-evaluated reclamation adds the units of active holds whose expires_at_utc <= now() back into the availability read via the partial index on (unit_type_id, stay_date) WHERE state = 'active', and the booking path runs the gated release for those holds in the same transaction as its own decrement, so the number it read is the number it can take.
- Make release an idempotent state transition gating an increment: UPDATE inventory_hold_night SET state = 'released' WHERE hold_id = :h AND stay_date = :d AND state = 'active', and increment ari_daily only when that statement reports exactly one row, in the same transaction. A bare increment is applied twice by a retried release, or by a sweeper racing a conversion, and sells a unit that does not physically exist.
- Set the TTL from data rather than roundness: too short and travellers lose the unit while typing a card number, too long and a sold-out date strands inventory in dead holds. Measure the checkout duration distribution and set the TTL near its p95, then track stranded-unit-minutes per sold-out date as the cost you accepted.
Worked solution 30 min
- Seed one unit type with remaining_units = 1 on three consecutive dates.
- Fire 200 concurrent three-night hold requests for that range; count successes and failures and read remaining_units on each date afterwards.
- Re-run with half the clients locking nights in reverse request order and record aborts, their SQLSTATE and their latency.
- Re-run under REPEATABLE READ without a retry loop, then with a bounded retry, recording the error class and success count for each.
- Let the winning hold expire without running the sweeper, read availability, then run the reclamation path and read again.
Follow-up
- Write out the interleaving for the deadlock: two three-night holds over overlapping ranges, locked in request order.
- Nights 1 and 2 decrement successfully and night 3 returns zero rows. What does the client see, and what is in the database one second later?
- oversell_allowance is 2. Where does it appear in your statement, who sets it, and on what evidence?
One region reports an extra night on every booking
Finance reports that for properties in one country, SUM(booking_night.night_price_minor + tax_minor + fee_minor) exceeds booking.total_minor and the night count is one greater than check_out_date - check_in_date. The booking table stores check_in_date and check_out_date as DATE; unit_type stores property_timezone as an IANA zone. The expansion that writes booking_night builds a timestamp from check_in_date, converts it to UTC, iterates adding 24 hours until it reaches the checkout instant, and casts each instant to a date. Deliverable: name every distinct defect, which zones and which dates each affects, and the correct expansion.
Approach
- Take one failing booking and print, side by side, check_in_date, check_out_date, every booking_night stay_date, the property's IANA zone and the process default zone. The shape of the difference identifies the defect: uniformly shifted rows are the round trip, one row past the end is the loop bound, a repeated date is the arithmetic.
- Name the round trip precisely. Midnight local converted to UTC and cast back to a date lands on the previous day for any zone east of UTC: local 2026-03-14T00:00 at UTC+09:00 is 2026-03-13T15:00Z. This fires every day of the year, not only at transitions, and it puts the arrival night outside the booking's own date range.
- Name the loop bound. Nights are the half-open interval [check_in_date, check_out_date); the checkout date is not a booked night. Iterating until the instant reaches or passes the checkout instant emits one extra row, which is the extra tax_minor and fee_minor breaking the sum identity.
- Name the arithmetic. Adding 24 hours is not 'the next day' in a zone that observes DST: the elapsed time between two consecutive local midnights is 23 or 25 hours across a transition, so after a transition the accumulated instants no longer sit on local midnight and the end test -- an accumulated instant compared against the checkout instant -- gains or loses a step depending on the direction of the transition. A zone that transitions at 00:00 has no local midnight on that date at all, so the start-of-day construction either throws or silently resolves to 01:00.
- Confirm by partitioning the mismatch: group the failing bookings by the property's UTC offset and by whether the stay range contains a transition. The round trip only moves the date for zones east of UTC -- local midnight at UTC+09:00 is 15:00Z on the previous calendar date, while local midnight at UTC-05:00 is 05:00Z on the same calendar date -- so an all-year mismatch confined to positive-offset properties is the round trip, and that is why the report names one country. The extra row from the loop bound appears in every zone; a night count that departs from that uniform off-by-one only on stays containing a transition is the arithmetic.
- Rewrite the expansion as calendar arithmetic over civil dates -- d = check_in_date while d < check_out_date, d = d + 1 day -- construct no instant at all, keep civil date and instant as distinct types, and backfill by recomputing booking_night rather than adjusting total_minor, since the property will bill the nights it actually served.
Follow-up
- The cancellation deadline is 18:00 local on the day before arrival. Which branch do you take when that local time occurs twice, and what do you store so the decision can be re-derived in a dispute months later?
- How do you find the remaining conversions of this shape in the codebase, and what fixture catches them in CI without waiting for the next transition date?
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Practice prompt ↗Practice prompt ↗03Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Practice prompt ↗Practice prompt ↗04Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Practice prompt ↗Practice prompt ↗Worked solution ↗05The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Practice prompt ↗Practice prompt ↗06Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Practice prompt ↗Practice prompt ↗07Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
For anything that touched live traffic, be ready to say how you would have undone it: a flag, a staged rollout, dual writes with the old path still authoritative. Once the old column is dropped or the source rows are overwritten there is no reverse, so name what you kept a copy of and for how long.
Where do you see yourself in the future?
Where do you see yourself in the future?
Approach
- Name the disagreement and how you resolved it with evidence.
- 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.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Describe your past projects and your specific leadership role in their…
Describe your past projects and your specific leadership role in their execution.
Approach
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Reverse your own decision on hold expiry
You chose sweeper-only expiry for inventory_hold_night: a job flips lapsed rows from active to expired and returns units, and the availability read trusts the state column alone. Production contradicted the choice. Describe a decision you reversed: what you originally believed and why it was reasonable, the specific observation that changed your mind, how long you waited before reversing, what the reversal cost, and what you kept from the first design. Name the metric that moved and one that did not.
Approach
- Say what made the original choice reasonable, because a reversal story with no defensible starting point reads as carelessness rather than judgement. Sweeper-only expiry keeps the read path trivial and lets the partial index on (unit_type_id, stay_date) WHERE state = 'active' answer availability with no clock in the query.
- Give the observation that broke it, in numbers. When the sweeper falls behind by L seconds, up to L seconds of lapsed holds still read as active, so availability is understated and searches lose bookable inventory on exactly the dates where holds are densest, which are the dates that sell. Availability understatement is the metric that moves first.
- Separate it from the opposite failure so the reversal is not mistaken for a panic. A sweeper that releases a hold the booking path has already converted increments capacity back and oversells, which is why the release must be conditional on the active-to-released transition and not a bare increment. Both failures are real and they pull in different directions.
- State the reversal concretely: evaluate expiry against the clock at read and write time, filtering active rows by expires_at_utc, and keep the sweeper for cleanup. Name the constraint that shapes it, namely that now() is not immutable so 'not expired' cannot be a partial index predicate; expiry is always a filter over the active set, which is exactly why the sweeper has to keep that set small.
- Report the cost honestly: a re-tested read path, a migration of in-flight holds, and a measurable increase in rows filtered per availability query. Then name a metric that did not move, because a candidate who claims everything improved is not measuring.
- Close on how long you waited and what you would watch earlier next time. Waiting for two data points beats reversing on one anecdote; waiting for a quarter is a different mistake.
Follow-up
- What is the right TTL for a hold, and what evidence would set it rather than a round number?
- How do you stop the sweeper from releasing a hold that the booking path converted a millisecond earlier?
- If the sweeper falls two minutes behind under load, what does a traveller see and what does the revenue graph show?
- 01
Where do you see yourself in the future?
- 02
Describe your past projects and your specific leadership role in their execution.
- 03
You chose sweeper-only expiry for inventory_hold_night: a job flips lapsed rows from active to expired and returns units, and the availability read trusts the state column alone. Production contradicted the choice. Describe a decision you reversed: what you originally believed and why it was reasonable, the specific observation that changed your mind, how long you waited before reversing, what the reversal cost, and what you kept from the first design. Name the metric that moved and one that did not.
Is this an official Porter Airlines Inc. interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Porter Airlines Inc.. Rounds and questions reflect what candidates have reported, not a process Porter Airlines Inc. has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the hiring process typically take?
The process can move relatively quickly once you reach the technical stages. Candidates should be prepared to commit to a series of interviews over a 2–4 week window.
PracHub interview research ↗What is the best way to prepare for technical rounds?
Focus on your core stack, specifically C# and SQL. Be prepared to discuss "how" you built something rather than just "what" you built, as the interviewers prioritize understanding your underlying logic.
PracHub interview research ↗Is there a specific culture I should be aware of?
Porter Airlines Inc. values professionalism and precision. Candidates who demonstrate a clear, respectful, and results-oriented communication style tend to perform better during the interview process.
PracHub interview research ↗What differentiates successful candidates?
Successful candidates are those who can connect their previous technical experiences to the specific challenges of the airline industry. Showing that you understand the "why" behind your technical choices is a strong differentiator.
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