Interview guides · Airlines Reporting
Airlines Reporting Software Engineer Interview Guide
Airlines Reporting Software Engineer interview guide: Java vs Python memory, multithreading and deadlocks, data structures, performance, API design.
The 60-second brief
Airlines Reporting Corporation (ARC) is described as providing data intelligence and distribution services in air travel. Its users are named as airlines, travel agencies and corporate travel partners, and the role description mentions the CI Experience Platform. Software Engineers there are described as working with APIs, data models and cloud infrastructure; AWS experience is listed as a nice-to-have. The role covers the software lifecycle for the products they support, and it involves working with product managers who turn customer needs into technical requirements, along with operations and design teams. For interview preparation, that means you should be able to explain the engineering side (services backed by large databases, concurrency, latency) and also how your work affects the airlines and agencies that use the data.
This guide is for Software Engineer candidates at Airlines Reporting whose main language is Java or Python. The reported requirements list either language plus API experience and a solid grasp of software architecture. AWS, a background in airline corporate travel sales and data-driven product work are listed as nice-to-haves. The guide covers the five reported technical questions, the two reported example scenarios (a latency optimisation story and a service over a large, complex database), the reported topics (AWS, deadlock resolution and concurrency control, technical problem solving, cloud infrastructure, Java) and the behavioral side of the loop.
Candidates describe an opening screen about their background, which can be a recorded video or a phone conversation. One or more later rounds then mix technical assessment with conversations with senior managers or team leads. No fixed round count or order has been reported.
Focus areas
- Amazon Web Services (AWS)
- Deadlock resolution / concurrency control
- Technical problem solving
Overview
The reported questions are mostly about fundamentals, not puzzle-style algorithms. You may be asked how Java and Python manage memory, why you would use multiple threads, how you choose a data structure, how you would find a production bottleneck, and how you design APIs that scale. A one-line answer to any of these is easy. Strong preparation means you can go one layer deeper: name the garbage collector, name the deadlock condition you would break, name the profiler you would run, and explain the pagination scheme you would choose and why.
The topics candidates report (AWS, deadlock resolution and concurrency control, Java, cloud infrastructure, technical problem solving) point to backend services that read and write large datasets. One reported scenario asks how you would design a service that works against a large, complex database. Prepare that as a small design, using an ARC-scale example such as billions of flight and ticket transaction records queried by agencies and airlines. Cover the API surface, the data-access layer, connection pooling, indexing and keyset pagination, and partitioning ticket or transaction tables by date. On the AWS side, name concrete pieces: RDS or Aurora with read replicas, ElastiCache for the cache and SQS for asynchronous work. Connect it to your answers on concurrency and performance, because the same service is where locks get contended and queries get slow.
Candidates describe the later stages as going deeper than the screen: specific technical scenarios plus a check of team fit, with senior managers or team leads involved. The role description stresses working with product managers and operations, and the work arrangement is reported as a hybrid model called WorkFlex. So the behavioral half needs concrete stories: a latency fix with numbers, an API decision you owned, a requirement you clarified with a product manager, and how you work well in a hybrid team. Use STAR, keep Situation short, and spend most of the answer on your own Actions and the measured Result.
Where to focus your preparation
No fixed sequence has been reported for this loop. Candidates describe a screen about their background followed by one or more rounds that get more technical and more team-specific. Treat this as one preparation track and confirm the exact format with your recruiter.
Preparation focus
Candidates report a screen first, either a recorded video response or a phone call, covering their background and interests. After that come one or more interviews that combine technical assessment with discussion led by senior managers or team leads. Candidates describe these later conversations as moving from resume-level questions toward specific technical scenarios and team fit. Use the screen to ask your recruiter how many rounds there will be, whether any of them involves writing code, and which team you are interviewing for. For the technical part, prepare spoken, layered answers to the five reported fundamentals questions, and be ready to sketch a service on a large database. For the discussion part, have STAR stories ready, including the reported latency optimisation prompt. If the screen is recorded, practise giving a two-minute background summary to a camera with no one reacting, so you are not doing it for the first time on the day.
What to demonstrate
- Depth in Java or Python internals and OS concepts: memory, processes, threads and concurrency
- Reasoning about performance and design for a service backed by a large database, including AWS where you have used it
- Clear explanation of technical decisions and their impact, in STAR form for experience questions
- Candidates report that the later conversations cover team fit; the role description stresses working with product managers and operations
How to prepare
- Write one-page answers to the Java versus Python memory question and the multithreading question, then say each out loud with a follow-up layer (GC algorithm, deadlock conditions)
- Sketch a service over a large, complex database: endpoints, connection pool, indexes, keyset pagination, cache, and the AWS services you would put it on
- Prepare a STAR story for optimising code that caused high latency, with before and after p99 numbers and the tool that found the cause
- Record a two-minute background answer for the video or phone screen, and list questions about team structure and next steps to ask the recruiter
Where candidates lose points
Answering the memory management question with 'Java has garbage collection and Python uses reference counting' and stopping there.
Prepare both layers. For Python, reference counting plus the cycle collector and pymalloc. For Java, generational tracing GC with G1 as the default, plus heap sizing. Also have one leak-hunting tool per language (Eclipse MAT for heap dumps, tracemalloc) so you can answer the follow-up about a leak in production.
Listing the benefits of multithreading with no mention of races or deadlock, even though deadlock resolution and concurrency control are reported topics.
Practise saying the four Coffman conditions and one concrete fix for each common case: global lock ordering, tryLock with a timeout, fewer shared mutable objects. Have the Python GIL caveat and the Java ExecutorService and virtual-threads point ready.
Answering the production bottleneck question with 'I would add a cache' or 'scale up the instances' before measuring anything.
Use a fixed sequence: quantify the symptom with p50 and p99, find the tier with traces, check saturation, then profile or EXPLAIN the suspect. Mitigate first, then fix, then confirm with the same metric. Name the tools: thread dumps, flame graphs, the slow query log.
Giving a generic REST checklist for the API design question instead of an experience answer.
Build one STAR story about an API you shaped, with the pagination, idempotency and versioning decisions you made and a measured result. Prepare a matching story for the reported prompt about optimising code that caused high latency, with real before and after numbers.
Treating the recorded video or phone screen as a formality and arriving without a short background summary or any knowledge of what ARC offers.
Write and rehearse a two-minute summary that connects your Java or Python, API and cloud work to a data-heavy service. Read about what ARC provides to airlines and travel agencies and about the CI Experience Platform, and prepare questions about the team and next steps for the recruiter.
Questions & practice
Can you explain the core differences between how Java and Python handle memory management?
Medium · Technical Fundamentals
Approach
- Java: objects live on the JVM heap, and each thread's stack holds local primitives and references. Memory is reclaimed by a tracing, generational collector. New objects go into the young generation (eden plus survivor spaces), and objects that survive move to the old generation. G1 has been the default since JDK 9, and ZGC and Shenandoah are the low-pause options. You set heap size with -Xms and -Xmx.
- CPython: every value is a heap object with a reference count, and an object is freed as soon as its count drops to zero. Because reference counting cannot reclaim cycles, a separate cycle collector (the gc module) finds them. It uses three generations through CPython 3.13, while CPython 3.14 introduced an incremental collector with young and old generations, so check gc.get_threshold() on the interpreter you use. Small objects of 512 bytes or less come from pymalloc arenas, so freed memory is often reused by the process and not returned to the OS.
- Practical consequences: CPython frees objects predictably, while the JVM decides when to collect and can pause threads to do it. Java tracing handles cycles natively. In both languages, release resources explicitly (try-with-resources in Java, with-blocks in Python) and do not rely on finalizers, which are deprecated in Java.
- Leaks in both languages are references you forgot about: unbounded static maps or caches, listeners that are never removed, ThreadLocals in thread pools. Name the tools you would use: heap dumps via jmap or jcmd analysed in Eclipse MAT, and GC logs, for Java. tracemalloc and objgraph for Python. Mention weak references (WeakHashMap, weakref) as one fix for caches.
- Mention the link to the GIL: CPython's reference counts are not atomic, which is one reason the default interpreter serialises bytecode execution. That leads naturally into the multithreading question.
Follow-up
- A long-running Java service slowly hits OutOfMemoryError. What would you capture, and how would you find the leaking reference?
- Why can a Python process's resident memory stay high after you delete a large list?
- What is a stop-the-world pause, and how would you reduce its impact on a latency-sensitive API?
What are the primary advantages of using multi-threading in an OS environment?
Medium · Technical Fundamentals
Approach
- Lead with the advantages, and give the reason behind each. Responsiveness: one thread blocks on I/O while others keep serving. Throughput: you can overlap network or disk waits. Parallelism: CPU-bound work can use multiple cores. Cost: threads share one address space, so they are cheaper to create and switch than processes and need no IPC to share data.
- Then give the costs right away. Shared mutable state causes race conditions, so you need synchronisation (mutexes, read-write locks, semaphores, condition variables, atomics). You also pay for context switches, and a crash in one thread can take down the whole process.
- Deadlock is a reported topic, so name the four Coffman conditions: mutual exclusion, hold and wait, no preemption, circular wait. Then give the fixes. You can prevent deadlock by breaking circular wait with a global lock order, or avoid hold-and-wait with tryLock and a timeout followed by back-off. You can also detect it with a wait-for graph, or in Java with jstack thread dumps or ThreadMXBean.findDeadlockedThreads.
- Add the language specifics. Java threads map to OS threads. Use ExecutorService pools rather than raw threads, and know that JDK 21 virtual threads make blocking I/O cheap. In standard CPython the GIL prevents CPU-bound threads from running bytecode in parallel, so use multiprocessing or native code for CPU work and threads or asyncio for I/O.
- Pool sizing: for CPU-bound work, use about the number of cores. For I/O-bound work, use more, roughly cores times (1 + wait time / compute time). Check the size by measuring under load.
Follow-up
- Two threads each need locks A and B, and the service sometimes hangs. How do you confirm it is a deadlock, and how do you fix it?
- When would you choose processes, or an async event loop, over threads?
- What is the difference between a mutex and a semaphore, and when is a read-write lock worth it?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Replace offset paging on the resource feed with keyset
Easy · keyset pagination · composite index · sort order
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
Approach
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
Follow-up
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
How do you determine when a specific data structure is the most efficient choice for a given problem?
Medium · Technical Fundamentals
Approach
- Start from the operation profile, not the structure. List the operations (lookup by key, insert, delete, ordered iteration, range query, min or max, membership, positional access), roughly how often each happens, the data size, and whether several threads share the structure.
- Map the profile to structures. Hash map or set: O(1) average lookup, no order. Balanced tree (TreeMap): O(log n) plus ordered and range queries. Dynamic array: O(1) index access, amortised O(1) append, good cache locality, O(n) middle insert. Heap: O(log n) push and pop for repeated min or max. Deque for queues and sliding windows, trie for prefixes. Adjacency list for sparse graphs, matrix for dense ones.
- Cover worst cases and constant factors. A hash map degrades with many collisions: in Java 8+ a bin is treeified into a red-black tree when it grows past 8 entries (TREEIFY_THRESHOLD), but only when the table capacity is at least 64 (MIN_TREEIFY_CAPACITY). Below that capacity, HashMap resizes the table instead, and a treeified bin reverts to a list at 6 entries. Resizing costs O(n) but amortises out. A linked list's middle insert is O(1) only once you already hold the node or iterator. Finding that position is an O(n) traversal, which is the main reason ArrayList usually wins, along with cache misses.
- Give a worked example in the travel domain: 'look up ticket transactions for an agency within a sales date range' points to a sorted structure (TreeMap.subMap, or bisect on a sorted list in Python) instead of a hash map. 'Top k routes by sales' points to a hash map for totals plus a size-k min-heap, O(n log k). Tying the choice to the business data shows curiosity about the domain.
- Close with how you check the choice: benchmark with realistic data (JMH in Java, timeit in Python) and watch memory overhead per element, because boxed objects in Java collections can cost far more than the raw values.
Follow-up
- When does an ArrayList beat a LinkedList even for frequent insertions?
- Which structure would you use for an LRU cache, and what is the complexity of get and put?
- How would your choice change if many threads read and write the structure at the same time?
Design the async export contract a client can resume safely
Hard · async operations · idempotency · leases
A tenant asks for a CSV of every resource. The work runs for minutes on the worker fleet through a job_run row carrying a lease, an attempt count and a dedupe_key, far past the edge's 400 ms budget. Callers are a browser that polls and a script that walks away and checks later. Specify what the submit call returns, the operation resource and its states, how a duplicate submit is handled, how a client learns about completion, what cancellation means given that a lease can expire mid-run, and how the result is fetched and when it expires.
Approach
- Split the API in two. Submit returns 202 with an operation id and a location to poll, and never blocks on the work. The operation is a real resource with its own lifecycle - queued, running, succeeded, failed, cancelled - plus attempt, a monotonic progress figure, and a terminal error drawn from the same code taxonomy the synchronous endpoints use, so a client needs one error vocabulary rather than two.
- Deduplicate at submit using job_run.dedupe_key, unique over (job_type, dedupe_key) while status is 'queued' or 'running': a repeat submit of the same logical export returns 200 with the existing operation instead of 202 with a new one, and the partial index deliberately permits a legitimate re-run once the first has finished. Pair it with the request's idempotency key so an HTTP-level retry of the submit is exact rather than merely similar.
- Tell the poller how to poll: Retry-After on the polling response, a minimum interval enforced at the edge, and a documented maximum lifetime after which an operation is reaped. Polling is the contract of record; the webhook is the fast path, and both must lead to the same terminal state, so a client that receives the completion event and then polls anyway sees no contradiction.
- Be exact about cancellation. A cancel request records intent; it cannot stop work already executing. The handler reads the flag at checkpoints, and because a lease expires on a clock that cannot distinguish a dead worker from a slow one, a second copy may start after the cancel was recorded - so the handler re-reads the flag immediately after claiming the lease. 'cancelled' becomes terminal only when no lease is outstanding; reporting it earlier shows a client a stopped job while a worker is still writing output.
Follow-up
- An operation has said 'running' for 40 minutes and the worker is gone. What does the client see, and which columns in job_run decide that?
- Two tenants each submit 50 exports at once. What in this contract stops one of them delaying the other?
Describe how you would troubleshoot a performance bottleneck in a production-level application.
Medium · Technical Fundamentals
Approach
- Define the symptom with numbers before forming any theory. Which endpoint or job? Is it p50 or only p99? Did throughput or the error rate change? When did it start? Then line that up against deploys, config changes, traffic shape and changes in dependencies.
- Locate the tier. Use distributed tracing or request timing (AWS X-Ray and CloudWatch, if the service runs on AWS) to split time across app code, database, downstream calls and queues. Then check saturation for each resource: CPU, memory and GC, thread pool, DB connection pool, disk and network I/O.
- Go deeper with the right tool for the tier. Database: slow query log, EXPLAIN ANALYZE, missing or unused indexes, N+1 query patterns, lock waits, pool exhaustion. JVM: GC logs, jstack thread dumps taken a few seconds apart to spot lock contention, and async-profiler flame graphs. Python: py-spy or cProfile.
- Mitigate before fixing: roll back, scale out, add a rate limit or turn off the expensive feature. Then fix the root cause, check the fix against the same metric that showed the problem, and add an alert or a load test so a regression is caught.
- Edge cases to name: tail latency made worse by retries without back-off, a slow downstream dependency that holds threads, and a cold cache after a deploy. This answer also gives you the structure for the reported story about optimising code that caused high latency.
Follow-up
- p99 latency doubled but p50 is flat. What does that suggest, and where do you look first?
- How would you confirm that lock contention, not CPU, is the bottleneck?
- How would you check the fix without putting risky load on production?
Suggested practice checklist
Each day covers one reported question or topic for this loop: runtime memory, concurrency and deadlock, data structures, production performance, API and database-backed service design, behavioral stories, and the screen. Every day ends with a written or spoken artifact you can reuse in the interview.
Day 1 — Java and Python memory management
- Write a one-page comparison: JVM heap and stacks, generational GC and G1, compared with CPython reference counting, the cycle collector and pymalloc
- Run a small experiment in each language: watch sys.getrefcount and gc.collect() on a reference cycle in Python, and run a Java program with -Xmx set low and GC logging on
- Write a short leak-hunting checklist for each language: heap dump with jmap or jcmd plus Eclipse MAT, and tracemalloc snapshots
- Answer the memory management question out loud, then answer its follow-up about a slowly leaking service
Deliverable: The comparison page, notes from the two experiments, the two leak checklists, and a recorded spoken answer with its follow-up.
Day 2 — Threads, processes, concurrency control and deadlock
- Write a two-lock deadlock in Java (or Python threading), reproduce the hang, capture it with jstack or faulthandler, then fix it twice (global lock order, and tryLock plus a timeout) and note the trade-off
- Write a one-page note on processes versus threads: fork/exec, context-switch cost, IPC options (pipes, sockets, shared memory) and scheduling basics
- Solve the peak-concurrency problem from lease records with a sweep over sorted start and end events
- Answer the multithreading question out loud, including the Coffman conditions, the GIL caveat and the processes-versus-threads follow-up
Deliverable: Deadlock repro code with two fixes and a thread dump, a processes-versus-threads note, a working peak-concurrency solution with its complexity, and a recorded answer.
Day 3 — Choosing data structures
- Make a table listing each operation (lookup, range query, min or max, ordered iteration, middle insert) next to the structure that serves it and its complexity
- Benchmark ArrayList against LinkedList inserts, or list against deque in Python, with JMH or timeit, and note where the result differs from the textbook complexity
- Work two examples aloud: ticket transactions for an agency within a sales date range (sorted structure), and top k routes by sales (hash map plus heap)
Deliverable: The operation-to-structure table, benchmark results with a one-line conclusion, and two worked examples.
Day 4 — Troubleshooting a production bottleneck
- Write a troubleshooting runbook: symptom metrics, tier isolation with traces, saturation checks, profiling tools, then mitigate, fix and verify
- Take a slow query on any sample dataset, run EXPLAIN ANALYZE, add the right index and record the before and after timings
- Solve the version-gap and relay-lag window function problem to practise the query patterns behind latency investigations
Deliverable: A one-page runbook, a before-and-after EXPLAIN comparison, and a working window-function query.
Day 5 — API design and a service over a large database
- Sketch the reported scenario of a service that works against a large, complex database: endpoints, data-access layer, connection pool, indexes, date partitioning, cache and AWS deployment
- Work the keyset pagination problem and write two sentences on why OFFSET degrades on a large table
- Outline the resumable async export contract, including idempotency keys, a 202 status resource and retries
- Answer the API design question as a STAR story about an API you built
Deliverable: A design sketch, a keyset pagination query, a written outline of the export contract, and a recorded API design story.
Day 6 — Behavioral stories in STAR form
- Write a STAR story for optimising code that caused high latency, with p50 and p99 numbers before and after and the tool that found the cause
- Write stories for reversing one of your own decisions and for estimating work you had never done
- Prepare an answer about turning a product manager's request into requirements, and one about staying productive in a hybrid team
Deliverable: Four or five written STAR stories, each with a measured result and a clearly stated personal action.
Day 7 — Screen rehearsal and full mock
- Record a two-minute background answer to camera, as for a video screen, and cut anything that does not connect to Java or Python, APIs or cloud work
- Read about what ARC provides to airlines and travel agencies and about the CI Experience Platform, and write how your experience applies to it
- Run a mock that combines the five fundamentals questions with two behavioral stories, then list questions for the recruiter and team leads
Deliverable: A recorded background answer, a short product note, mock notes listing your weakest answer, and your list of questions to ask.
Behavioural preparation
Candidates describe the later conversations, often with senior managers or team leads, as covering past projects and team fit alongside technical scenarios. The role involves working with product managers and operations in a hybrid setup, so prepare STAR stories that show your own actions, a measured result and how you worked with non-engineers.
- Explain a time you optimized a piece of code that was causing high latency.
- Tell me about a time you turned a vague request from a product manager into concrete technical requirements, and what you had to clarify first.
- Describe how you stay productive and connected to your team when part of the week is remote.
- Describe a technical decision you made and later reversed: what changed your mind, and what the reversal cost.
What is your experience with API design principles, and how do you ensure they are scalable?
Medium · Technical Fundamentals
Approach
- Treat it as an experience question. Pick one API you designed or changed significantly, describe it in two sentences (who calls it, rough request volume, what data it returns), and say which decisions were yours.
- Cover the design principles through that API, not as a list. Resource-oriented URLs with correct HTTP methods and status codes. A consistent error body. Idempotency, where PUT and DELETE are idempotent by definition and POST is made safe to retry with an idempotency key. Cursor or keyset pagination instead of OFFSET on large tables. Versioning plus additive, backward-compatible changes.
- Then cover scalability. Keep the service stateless so it scales horizontally behind a load balancer. Cache with ETag and Cache-Control or a server-side cache. Rate-limit per client. Use timeouts and retries with exponential back-off and jitter. Move long-running work to an async pattern (202 Accepted plus a status resource). On AWS that could mean API Gateway or an ALB in front of autoscaled compute, with a queue for the async path.
- End with a measured Result: latency, throughput, error rate or client adoption before and after, and one thing you would change now. The common mistake is reciting REST rules without a single decision you made or a number to back it up.
Follow-up
- How would you rename or remove a response field without breaking clients you do not control?
- Why does OFFSET pagination slow down on a large table, and how does keyset pagination fix it?
- How would you design rate limiting for an API with a few very heavy clients?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?
Estimate work you have never done and defend the range
Hard · estimation · backfills · expand-contract
You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.
Approach
- Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
- Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
- Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
- Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.
Follow-up
- How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
- Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?