Software Engineers at Palantir build software on platforms such as Foundry or Gotham. Those platforms bring together data from many different sources and support analytics and operational decision-making. The role description covers the full life of a feature (architectural design, implementation, testing and deployment) and work on the data plumbing that connects backend infrastructure to the user-facing applications built on it.
The reported questions are mostly about implementation. You build a session manager class with start_session and get_allocation, balance allocations across servers, handle duplicate session IDs, model a road network with Location, Road and RoadConnection classes, and find the shortest distance between two cities with BFS when every weight is 1 and Dijkstra when weights differ. The question bank for this role also covers weighted interval scheduling, word search with DFS and backtracking, sliding window, product of array except self, an in-memory database with transactions, a payment race condition to debug, grid-based spatial indexing, and SQL on usage logs and share events.
The listed must-have skills are proficiency in at least one object-oriented language (Java, C++ or Python are the examples), a solid grasp of data structures and algorithms, and experience with multithreaded programming and concurrency. The nice-to-haves are distributed systems, graph or network modelling, and debugging performance bottlenecks. Prepare to justify each design decision out loud, not just to reach working code, because the source notes say standard problems are often changed to see how you adapt.
PracHub has no confirmed round sequence for this role, and the notes say the process can vary by team or office. Treat the categories below as preparation areas and confirm the format with your recruiter.
Preparation focus
editorialNo round sequence has been confirmed for Palantir Software Engineer candidates. The source notes describe a mix of technical sessions and discussion of past projects, so prepare both. The reported technical questions cover implementation-heavy coding (session management, a road-network graph), shortest paths, multithreading and data consistency, and a monitoring-system design. Resume questions ask you to walk through a complex project and defend the decisions in it. Confirm the format and number of sessions with your recruiter.
What to demonstrate
- Choosing the right data structure and stating its complexity on implementation questions: heaps and hash maps for session allocation, adjacency lists with BFS or Dijkstra for road networks
- Reasoning about concurrency and data consistency when shared state is updated or data is collected from many servers
- Asking clarifying questions, breaking the problem down and explaining your approach before and while you code
- Defending the technical decisions in your own past projects
How to prepare
- Implement the session manager (start_session, get_allocation, duplicate IDs) and the Location/Road/RoadConnection graph from scratch in your strongest object-oriented language, with tests
- Design a monitor that collects metrics from 1000 servers every ten minutes, then write down its threading and consistency trade-offs
- Practise a standard problem with one changed constraint, so adapting a known solution becomes routine
- Prepare two resume projects to the level of why each architectural choice was made and what you rejected
7 candidate reports. Individual accounts describe a particular role and hiring cycle.
Palantir Intern Software Engineer Interview Experience — Three-Part FDE OA Building a Grocery Coupon System, Watch the Rounding
I got the OA and worked through it in about an hour and a half. Nothing was especially hard, but watch out for rounding. I've organized everything and posted it all below. Part 1 — Initial Problem You're building a discount system for Trader Yojoe's, a growing grocery chain. Their business is thriving, but they're starting a new pilot program where shoppers can bring coupons and apply them to ite…
Read full experiencePalantir Software Engineer 30-minute deployment strategist screen
I expected a heavy technical process, but the first call surprised me. I had a 30-minute phone screen with a deployment strategist, and it went very well. We talked mainly about my resume, projects, and general background. It felt like a conversation about how I think and what I had been doing, not an immediate coding test. I did not receive an offer. What stayed with me was that the early stage…
Read full experiencePalantir Forward-Deployed Engineer interview: referral, decomposition, and long decision wait
After applying with a referral, I went through an HR screen and a decomposition round, then an onsite with three separate interviews on coding, learning, and decomposition. A final hiring-manager conversation ended the process. The format felt deliberate. Even before the onsite, the rounds appeared to build toward the same skill: taking an open-ended situation and making it actionable through bot…
Read full experienceForward-Deployed Engineer interview at Palantir: collaborative debugging
After I applied, a recruiter reached out and the process moved quickly into a technical conversation. I expected the recruiter to conduct the early screen, but an engineer joined instead. The exchange felt collaborative: I could ask questions, explain my thinking, and offer different approaches. The technical work was mostly coding and problem solving. One session was a straightforward LeetCode-l…
Read full experiencePalantir Software Engineer interview experience
The process began with a basic recruiter call, then an OA, followed by a first technical round that already felt like a moving target. I had to do decomp and debug in the interview flow. The final technical round combined coding and behavioral questions. The journey had a coin-flip quality: it seemed possible to be solid technically and still not proceed, or to perform roughly and still advance d…
Read full experiencePracHub editorial advice for the preparation topics above.
Coding the session manager before deciding what get_allocation returns and what a duplicate session ID does
Before you write code, state the invariants: which allocation a new session gets, whether freed allocations are reused, and what happens when start_session sees an ID it already holds (return the existing allocation, or reject it). A common approach is a min-heap of free allocation IDs plus a hash map from session ID to allocation, which gives O(log n) assignment and O(1) lookup. For balancing across servers, say what 'balanced' means (fewest active sessions, for example) and keep servers in a heap keyed on that load. Then check that a released allocation is not handed out twice.
Using BFS on a weighted road graph, or not saying when Dijkstra is valid
The reported question separates the two cases, so name the weight assumption out loud. BFS finds shortest paths only when every road has the same weight, in O(V + E). With other weights, use Dijkstra with a priority queue in O((V + E) log V), skip stale heap entries, and state that it needs non-negative weights. Model Location, Road and RoadConnection so that adjacency lookup is a map read, not a scan over every road, and test an unreachable destination and a source equal to the destination.
Designing the server monitor around throughput and ignoring slow or unresponsive servers
1000 servers every ten minutes averages under two collections a second, so raw load is not the hard part. Talk about what actually breaks: one hung server blocking a sequential loop, an unbounded thread per server, and partial results written while a collection cycle is still running. Use a bounded worker pool with per-server timeouts, record missing data as missing rather than zero, and say how shared state (the latest reading per server) is protected: locks, a concurrent map, or single-writer ownership, and what each costs.
Reciting a memorised solution after the interviewer changes a constraint
The source notes say standard questions are often modified. When a familiar problem appears, restate it, ask what is fixed (input size, weights, duplicates, concurrency) and check whether the change breaks the textbook approach before you use it. If a new constraint arrives mid-solution, say which part of your code it invalidates and change that part only.
Describing a resume project without being able to defend its decisions
Resume deep dives ask for the reasoning behind your technical choices. For each project you plan to discuss, write down the main architectural choice, the alternative you rejected and why, one technical obstacle and how you diagnosed it, and what you would change now. A list of accomplishments with no reasoning behind it is the weak version of this answer.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Implement a session manager class with start_session and get_allocatio…
Implement a session manager class with start_session and get_allocation functions.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
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?
What approach do you take to handle duplicate session IDs?
What approach do you take to handle duplicate session IDs?
Approach
- Choose the data structure from the access pattern, not from familiarity.
- 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.
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?
Implement a graph structure for city roads, including classes for Loca…
Implement a graph structure for city roads, including classes for Location, Road, and RoadConnection.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- 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.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
How do you ensure session allocation is balanced across servers?
How do you ensure session allocation is balanced across servers?
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.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Hold a tenant to a trailing sixty-second request limit
The gateway must hold each tenant to R requests in any trailing 60 seconds, in aggregate across three regions and every pod, within a budget of under 10 ms added p99. Peak is 30,000 requests/second across 200,000 active tenants, and traffic is heavily skewed toward a handful of them. Give an exact single-process algorithm with its amortised per-request cost and its memory per tenant, then a bounded-memory approximation and the worst-case overshoot it actually admits. Say what the distributed version does when the counter store is unreachable.
Approach
- Exact, single process: a per-tenant deque of request timestamps. On arrival, pop from the front while
front <= now - 60s, then admit if the remaining length is below R and push. Each timestamp is pushed once and popped once, so the cost is O(1) amortised. The O(R) version is the one that re-filters the whole deque on every request. - Quote the memory. R = 1,000 across 200,000 active tenants is up to 2 x 10^8 timestamps at 8 bytes, about 1.6 GB, and that is the worst case rather than the mean, because the long tail of small tenants holds almost nothing. Skew helps you here and hurts you in the sharding decision.
- Bounded alternative, with its real bound stated: a fixed 60-second counter is O(1) memory but admits close to 2R across a 60-second span straddling a boundary. The weighted two-bucket estimate,
prev * (60 - elapsed)/60 + cur, is better on smooth traffic but assumes the previous window's arrivals were uniform; an adversary packing them at the end of that window is undercounted and can still approach 2R. Say that rather than calling it exact. - Token bucket is the usual gateway answer and a different contract: O(1) state per tenant (
tokens,last_refill), a sustained rate, and a deliberate burst allowance equal to the bucket size. Choose it when a burst is acceptable and the log when the limit is contractual. - Distributed: the limit is per tenant in aggregate, so a local bucket of R/N per pod is wrong in both directions under skew. A tenant landing on one pod is throttled at R/N, and a tenant spread evenly across pods exceeds R. The shared check must be a single atomic round trip, one script or one increment-and-compare, never read-then-write, and it must fit inside the 10 ms p99 budget.
- Decide the unavailable case in advance and write it down. Failing open keeps the product up and lets a tenant exceed its limit for the duration; failing closed converts a counter-store outage into a full outage. Most gateways fail open on rate limits and closed on authorisation, and those are two separate decisions made separately.
Worked solution 25 min
- Implement the deque version and instrument the per-request pop count, then confirm total pops equal total pushes over a run.
- Generate a burst that places R requests in the last 100 ms of one minute and R more in the first 100 ms of the next.
- Run that burst through the exact deque, a fixed 60-second counter, and the weighted two-bucket estimate, recording admissions in the trailing 60 seconds at every instant.
- Size the memory as R x active tenants x 8 bytes at R = 1,000 and 200,000 tenants, and compare it against what a token bucket would need.
Follow-up
- One tenant sends 40% of all traffic. What does that do to a single counter key, and what do you shard on instead?
- Quotas rather than rate limits: the check is
select used; if used < limit then insert. Name the isolation level that still permits the overshoot, and the two fixes. - How do you return an accurate
Retry-Afterfrom the exact algorithm without a second scan?
Enforce a concurrent-run quota that survives simultaneous requests
A plan allows at most 20 concurrently running rows in job_run per tenant. The table holds run_id, tenant_id, workspace_id, status (queued, leased, running, succeeded, failed, timed_out, cancelled, lost), lease_token, leased_until, started_at and finished_at. Today the service runs select count(*) from job_run where tenant_id = $1 and status = 'running', compares the result to 20, then inserts. Under load a tenant exceeds the cap by exactly the number of concurrent requests. Name the anomaly, say which isolation levels do and do not prevent it, and give a version that holds, as SQL.
Approach
- Name it: write skew. Each transaction reads a predicate (the count of running rows), neither modifies what the other read, and both then insert rows that jointly violate an invariant no single row expresses. Read committed permits it. So does repeatable read, because snapshot isolation's first-updater-wins check fires only on conflicting row updates, and these are inserts touching disjoint rows.
- Enumerate the fixes with their real costs. SERIALIZABLE works: PostgreSQL's SSI tracks the predicate read and aborts one transaction with SQLSTATE 40001, which obliges the caller to retry and makes the abort rate rise with contention on a hot tenant. Folding the predicate into the write as
insert ... select ... where (select count(*) ...) < 20narrows the race to the statement's snapshot but does not close it under read committed. - Give the version that holds at read committed: serialise on a row both transactions must touch.
update tenant_concurrency set running = running + 1 where tenant_id = $1 and running < 20 returning runningupdates zero rows when the cap is reached, and zero rows is the rejection. This works because at read committed a blocked UPDATE re-evaluates its WHERE clause against the newly committed row; at repeatable read the same statement raises a serialisation error instead, so the isolation level changes the calling contract. - State the cost you just bought. That row is now a per-tenant serialisation point, so admission throughput for the tenant is bounded by one divided by the lock hold time; at a 2 ms hold that is roughly 500 admissions/second. Keep the critical section to the single UPDATE, with no network call or scheduling decision inside the transaction, and decrement in the same transaction that writes the terminal status.
- Close the leak the status enum implies: a run can end as
lost, so a crashed worker otherwise consumes a slot forever. Reconcile on a schedule againststatus = 'running' and leased_until < now(), and treat the counter as a fast path overjob_run, which stays the system of record.
Follow-up
- Write the retry loop for the SERIALIZABLE version. What does the caller see when it keeps aborting, and what bounds the retries?
- Two regions each keep a counter. What is the effective cap, and what does admission do when the counter store is unreachable?
- The cap changes mid-flight on a plan upgrade. Do running jobs get killed, and what does the counter row look like during the change?
Model credential revocation so history survives the delete
tenant_api_key stores key_id, tenant_id, workspace_id, name, key_prefix, secret_hash, scopes text[], status (active, revoked, expired, compromised), auth_version, created_at, expires_at, last_used_at, revoked_at, revoked_reason. Rotation inserts a new row and revocation never deletes, because an incident review asks which credential served a request last quarter. Write the constraints that enforce: a label is unique only among a tenant's live keys, revoked_at and status can never disagree, and scopes is never empty. Then write the authentication lookup predicate, and name one column in this table that must stay out of it.
Approach
- Reach for a partial unique index rather than a plain UNIQUE:
create unique index on tenant_api_key (tenant_id, name) where revoked_at is null. Any number of revoked rows may share a label, the live namespace stays unique per tenant, and the revoked majority is not in the index at all, so it stays small on a table that only grows. - Tie the nullable timestamp to the enum so the two cannot drift:
check ((revoked_at is not null) = (status in ('revoked','compromised')))andcheck ((revoked_at is null) = (revoked_reason is null)). A revocation that records no reason is the one an incident review cannot use. - Write the emptiness check as
check (cardinality(scopes) > 0), notarray_length(scopes, 1) > 0. array_length returns NULL for an empty array, a CHECK constraint passes when its expression is NULL, so the array_length version accepts exactly the value it was written to reject. - Make the lookup a single index probe with every liveness condition inside it:
where secret_hash = $1 and revoked_at is null and (expires_at is null or expires_at > now()) and auth_version = $2, backed by a unique index on secret_hash. Nothing is filtered in application code, so there is no path that forgets a clause. - Keep last_used_at out of that predicate. It is written asynchronously and is allowed to lag by a minute, so it is a usage signal; feeding it into an authorisation decision makes the decision depend on a write that may be late, batched away or lost.
- Flag the modelling smell while you are here:
expiredis derivable fromexpires_at < now(), so storing it as a status obliges a job to keep it true and guarantees the column is wrong between the expiry instant and that job's next run. Derive it in the predicate; keep the stored status for states that are decisions rather than clock readings.
Worked solution 20 min
- Create the table with all three constraints on a scratch database and insert two revoked rows sharing (tenant_id, name); the partial index should accept both.
- Insert a second live row with that same name and confirm the violation names the partial index.
- Run
update tenant_api_key set revoked_at = now()leaving status = 'active' and confirm the CHECK rejects it; then tryinsert ... scopes = '{}'against both the cardinality and the array_length forms and note that only one rejects it. - Run
explain (analyze, buffers)on the lookup predicate for a live key and confirm an index scan on secret_hash with rows removed by filter equal to zero.
Follow-up
- Rotation issues a replacement while the old key stays live for a 30-day overlap. What does the uniqueness rule become, and what does the UI show to tell two same-named keys apart?
- A password reset bumps the principal's auth_version. No row in this table changed. How does the next request fail, and what query counts how many keys that bump just killed?
- A key turns up in a public repository. Which columns let you find it, and what do you write to the row?
Leasing sandboxed job runs without double execution
job-runner leases work from a queue and executes customer workloads in sandboxes with CPU, memory, wall-clock and egress limits. Run durations span 200 ms to 30 minutes, thousands are concurrent, and job_run.status must reach exactly one terminal state among succeeded, failed, timed_out, cancelled and lost. A worker can pause 45 seconds for garbage collection, or be briefly partitioned, after its sandbox has exited but before it writes the outcome. Design the lease duration, the fencing, and timeout ownership, and state how billable_seconds is computed for a run whose worker never returns.
Approach
- Resolve the lease-duration conflict rather than picking a compromise. A lease shorter than the longest legitimate run re-dispatches work that is still executing; a lease long enough for a 30-minute run leaves a crashed 200 ms run undetected for half an hour. Use a short lease - about 30 seconds - renewed by a heartbeat every 10 seconds while the supervisor is alive, each renewal pushing leased_until to now() + 30 s. Detection latency is then one lease, not one heartbeat: a dead worker's last successful heartbeat landed at most 10 s before it died, so leased_until expires 20 to 30 s after the death, plus whatever interval the reaper scans on. What the 3:1 ratio of lease to heartbeat buys is headroom - one or two missed heartbeats change nothing, and a stall only becomes a re-dispatch once it outlives the lease remaining when it began, between 20 and 30 s here. Both costs follow from those two numbers: heartbeat write load proportional to concurrent runs (3,000 runs at a 10-second interval is about 300 updates/second), and any pause longer than the lease - the 45-second GC - re-dispatching a run that is perfectly healthy.
- Fence the outcome write so the store, not the worker's memory, arbitrates: UPDATE job_run SET status = $1, finished_at = $2, exit_code = $3 WHERE run_id = $4 AND status = 'running' AND lease_token = $5, and require exactly one row affected. Zero rows means this worker was fenced while it was paused, and its correct behaviour is to discard the result, not to retry - the retry is how a resumed worker overwrites a newer attempt.
- Make the status machine monotone with a check constraint or a trigger so no terminal row can move, and model a retry as a new row with parent_run_id set and attempt incremented rather than a reset of the old one. That is what keeps how many times did this actually execute answerable afterwards and keeps each attempt's resource usage attributable to itself.
- Put the wall-clock timeout in the supervisor, never in the workload: a customer-supplied program asked to time itself out will not. Distinguish timed_out, which the supervisor observed and caused, from lost, which nobody observed at all, and leave exit_code null unless the supervisor actually saw the process exit. Recording lost as failed asserts an outcome no one witnessed and then bills and retries on that assertion.
- Decide billable_seconds for the unobserved case explicitly, because it cannot be derived. It is not now() - started_at, which bills queue and pause time the customer never consumed. The two defensible policies are leaving it null while status = 'lost' and billing nothing, or flooring it at the last heartbeat's observed running time as a stated lower bound; pick one and write it down. Add a per-tenant concurrency cap on sandbox slots so one tenant cannot occupy the whole fleet while these questions are being answered.
Worked solution 30 min
- Draw the timeline: last successful heartbeat at t, sandbox exits at t, worker pauses from t to t+45 s, so leased_until = t+30 s and the lease expires there. Mark where the second worker starts and where the first worker's write arrives, then say how short the pause would have had to be to change nothing.
- Write the fenced terminal update and state the expected rows-affected in both the healthy case and the fenced case.
- Size the heartbeat: 3,000 concurrent runs at a 10-second interval is about 300 row updates/second on job_run - decide whether that lands on the same table and what it contends with.
- Write the billable_seconds rule for each terminal status and say which status leaves it null.
Follow-up
- A worker returns from a 45-second GC pause and writes succeeded. Walk through what the database does statement by statement.
- Heartbeats are now a few hundred writes a second against job_run. How do you keep that off the path that matters, and what do you lose by moving it?
- The re-dispatched workload has an external side effect the first attempt already performed. What does your design owe the customer, and what can it not fix?
Write the delivery guarantee and replay API for outbound events
The webhook-delivery service fans events to customer endpoints, tracked in webhook_delivery (subscription_id, tenant_id, event_id, redelivery_seq, status, attempt_count, next_attempt_at, lease_token, last_response_code, payload_digest). Customers are asking for exactly-once and in-order delivery. Write the contract you can actually honour: the guarantee, the headers that make it usable, which customer response codes are retryable, the retry schedule and terminal condition, what happens to an endpoint that has been down for a day, and the shape of the redelivery endpoint. State plainly what you are not promising and what the customer must do instead.
Approach
- Refuse exactly-once with the mechanism rather than with policy: the customer's acknowledgement can be lost after they have already committed, so the sender cannot distinguish unprocessed from processed-but-unacknowledged and must retry. The deliverable is at-least-once with a stable event identifier and the customer documented as the deduplicating party.
- Price ordering instead of promising it. With parallel attempts per subscription, a retried event overtakes a newer one, so in-order delivery requires a single in-flight attempt per subscription, which turns any slow endpoint into head-of-line blocking for that subscription's whole queue. Offer it per subscription with that cost written down.
- Define the response contract from the customer's side: 2xx is accepted, 408, 429 and 5xx are retryable, 410 disables the subscription, and every other 4xx is permanent and terminal. Publish the read timeout and tell the customer to acknowledge first and process asynchronously, since their processing time otherwise consumes your worker occupancy.
- Publish the schedule and its end: capped exponential backoff with full jitter, sleeping uniformly in [0, min(cap, base * 2^attempt)] so a mass failure does not re-synchronise the herd, terminal after a stated attempt count or age, plus a per-endpoint circuit breaker that records further deliveries as dropped_circuit_open and notifies the owner rather than burning shared worker capacity.
- Shape redelivery as an insert, not a reset: POST to a redeliveries collection with event ids or a time window creates rows at redelivery_seq + 1 carrying the same bytes, which payload_digest lets you prove, leaving the original terminal rows intact as the record of what happened.
- Compare the event's tenant against the subscription's tenant at enqueue and again immediately before signing, because cross-tenant delivery originates in an enqueue path that took the subscription from one lookup and the payload from another, not in the worker.
Follow-up
- A customer wants everything they missed during their six-hour outage. Do the dropped_circuit_open rows let you answer that, and what retention bound does the answer depend on?
- You offer the ordered mode and one customer's endpoint slows to two seconds per request. What do their delivery metrics look like, and what do you owe them in the docs?
A rare job-run overwrite that logging makes disappear
About one job run in fifty thousand records billable_seconds matching no observed sandbox lifetime, and a few rows show worker_id changing after finished_at was already set. It does not reproduce: debug logging around the terminal write made it vanish for two weeks before it returned. Runs last from 200 ms to 30 minutes, the lease is 60 seconds and is renewed while a run executes. Give an ordered checklist, the mechanism, and a fix that makes the illegal write impossible rather than merely rarer.
Approach
- Mine the evidence instead of chasing a repro: select rows where updated_at is later than finished_at, or where a terminal status was written twice, and join them to attempt history to recover both writer identities. The defect has already happened tens of times and the rows are the recording.
- State the signature before measuring it. If the cause is a lease that expired while the original worker was stalled, affected runs should cluster where the gap between the last renewal and the terminal write exceeds the lease, and should correlate with worker pause metrics rather than with workload shape.
- Read the disappearance honestly. Logging inside the window changed the timing and lowered the probability; it is evidence about how narrow the window is, not a fix. Reproduce by widening the window on purpose, shortening the lease and injecting a pause between sandbox exit and the terminal write, rather than by adding more instrumentation.
- Name the mechanism precisely: the lease expires during a stall such as a long garbage-collection pause or a brief partition, the run is re-dispatched, and the original worker then wakes and writes its terminal state over the new attempt's row. A lease alone cannot stop this, because the check and the write are separated by the stall.
- Fix by fencing the write itself: UPDATE job_run SET status = $2, finished_at = $3, billable_seconds = $4 WHERE run_id = $1 AND status = 'running' AND lease_token = $5, with zero rows affected interpreted as having been fenced rather than as success. The token lives on the row so the store arbitrates, not the worker's memory.
- Keep the state machine honest: a retry inserts a new row pointing at parent_run_id rather than resetting the old one, and a run whose worker vanished terminates as lost with billable_seconds null, because recording failure asserts an outcome nobody observed and then bills and retries on that assertion.
Follow-up
- The supervisor also emits a usage event on completion. What does the fenced worker do about the event it already emitted, and how does metering absorb it?
- Lease renewal is itself a network call. What happens when a renewal times out, and how does the worker decide whether it still holds the lease?
- Why is lengthening the lease past the longest legitimate run the wrong lever, and what breaks if you do it anyway?
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Session manager and allocation
- Implement a session manager class with start_session and get_allocation, using a min-heap of free allocation IDs and a hash map from session ID to allocation; state the complexity of each operation.
- Decide and write down how a duplicate session ID is handled (return the existing allocation, or reject it), then add a test for it.
- Extend the design to balance sessions across servers: define the load metric and keep servers in a heap keyed on it.
- Write tests for releasing and reusing an allocation, and confirm the same allocation is never held by two sessions.
Deliverable: A tested session manager class with written invariants for allocation, release, duplicates and balancing.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Road networks and shortest paths
- Model a city road network with Location, Road and RoadConnection classes backed by an adjacency list.
- Implement BFS for unit-weight shortest distance and Dijkstra with a priority queue for other weights; state each one's complexity and Dijkstra's non-negative-weight requirement.
- Test an unreachable city, a source equal to the destination, and a graph where BFS and Dijkstra give different answers.
- Solve word search with DFS and backtracking from the question bank, and explain how you avoid revisiting a cell.
Deliverable: A graph class with both shortest-path methods and a test list that shows where BFS gives the wrong answer on weighted roads.
Practice prompt ↗Practice prompt ↗03Bank coding patterns
- Solve the question bank's sliding-window, product-except-self and weighted interval scheduling problems, and state the complexity of each before coding.
- Work through the worked exercise 'Hold a tenant to a trailing sixty-second request limit' to practise sliding-window reasoning under changed constraints.
- Pick one bank problem and change a constraint yourself (duplicates allowed, input streamed, weights added), then adapt your solution and explain what broke.
Deliverable: Three solved bank problems with complexity notes and one written adaptation to a changed constraint.
Practice prompt ↗Practice prompt ↗04Concurrency and debugging
- Write a short example of a race condition on shared state (two updates to one balance or counter) and fix it three ways: a lock, an atomic operation, and single-writer ownership.
- Work the question bank's payment race condition problem, saying out loud how you would confirm the diagnosis before fixing it.
- Work through the worked exercise 'Leasing sandboxed job runs without double execution' and the debugging drill on a rare job-run overwrite, focusing on fencing a write that a paused worker may make late.
Deliverable: A one-page note on race conditions with three fixes, each with its cost, and a written diagnosis of the payment race.
Practice prompt ↗Practice prompt ↗Worked solution ↗05System design: monitoring and data backends
- Design a monitor that collects metrics from 1000 servers every ten minutes: collection model, worker pool size, per-server timeouts, storage, and how missing data is recorded.
- Write out the threading and data-consistency trade-offs in that design, including what a reader sees while a collection cycle is only partly done.
- Sketch one more bank design topic (employee lookup, grid-indexed backend, or an in-memory database with transactions) to endpoint and data-model level.
Deliverable: Two design sketches, each with its failure cases and the concurrency choices explained.
Practice prompt ↗Practice prompt ↗06SQL and data manipulation
- Solve the question bank's SQL questions on active users and ranking users from usage logs, and explain your join and aggregation choices.
- Work through the share-events questions (shares at specific dates, final holdings by date) and handle events that fall exactly on the query date.
- Complete the worked exercise 'Model credential revocation so history survives the delete' and verify each of its checks.
Deliverable: Working queries for the bank's SQL and holdings questions, with the edge cases each one handles written next to it.
Practice prompt ↗Practice prompt ↗07Resume deep dive and a changed-constraint mock
- For two resume projects, write the main architectural choice, the rejected alternative, one technical hurdle and what you would change now.
- Prepare answers to the reported behavioral questions: a complex project walkthrough, why Palantir, and handling trade-offs between conflicting requirements.
- Run a mock where a partner changes a constraint partway through a session-manager or shortest-path problem, and record how you adapted.
Deliverable: Written notes for two resume projects plus notes from the mock on how your solution changed.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The reported behavioral questions focus on your resume and your reasoning: a complex project and its technical hurdles, why you want to work at Palantir, and how you handle conflicting requirements. Prepare every story to the level of the decision you made, the alternative you rejected, and what the outcome taught you. Expect follow-ups that ask why, not just what.
Reverse a webhook ordering decision after measuring its cost
You argued for strict per-subscription ordering in webhook-delivery, which means one in-flight attempt per subscription. It shipped. Three months later a single unresponsive endpoint holds one subscription's queue at a six-hour backlog, and two customers report events arriving out of order anyway once their own retries are counted. Describe a decision you reversed: what you originally optimised for, the measurement that changed your mind, what the reversal cost in engineering time and customer change, and how you told the people who had already built on the original guarantee.
Approach
- State the original decision as a trade you made knowingly. Ordering across a network requires a single in-flight attempt per subscription, and its price is head-of-line blocking whenever one endpoint is slow. 'We priced it wrong' is a much stronger opening than 'we did not realise', and it is usually the true one.
- Bring the measurement that flipped it, not the anecdote: backlog age at the ninety-ninth percentile per subscription, the share of subscriptions where one slow endpoint gated an otherwise healthy queue, and the delivery throughput lost to serialisation. A reversal justified by complaints is indistinguishable from a reversal justified by fatigue.
- Name what you learned about the guarantee itself, which is the engineering content of this story. At-least-once delivery means a retried event already arrives after newer ones and the consumer already must be idempotent, so a guarantee the customer has to defend against anyway was never worth what it cost to provide.
- Describe the migration, because reversing a published contract is the hard half and the part candidates skip. Parallel attempts behind a per-subscription flag, a monotonically increasing sequence number added to the envelope so order-sensitive consumers can sort or discard, documentation that states at-least-once and unordered in those words, and a deprecation measured in quarters because the client is a pinned SDK inside a build pipeline you cannot see or redeploy.
- Give the cost in the two currencies that matter: engineer-weeks, and how many customers had to change code. Then say who you told before it shipped rather than in a changelog afterwards, and which large customer you left on the old behaviour and for how long.
- Close with the signal you now weight differently, stated as something you would do earlier next time: measuring the blocking cost on the slowest decile of endpoints before committing to the guarantee, rather than after a customer noticed.
Follow-up
- A customer insists they need ordering. What do you offer them that is not global serialisation?
- How did you choose the deprecation window given that you cannot see or redeploy the clients?
- What would have to be true for you to reverse back?
Ship metered billing with a named deduplication horizon
Metered billing must be on in three weeks. usage_event is partitioned daily, so its unique index must include the partition key and deduplicates only within a day: a producer retry that crosses midnight, or a replay run a week later, gets through. A cross-partition dedup store is two weeks you do not have. Describe shipping with debt you named in advance: what you shipped, what you wrote down, the detector you added, the trigger and date for paying it off, and what you would have refused to ship under the same pressure.
Approach
- Show you can separate the two kinds of debt, because that distinction is what the question actually probes. Debt that costs engineering time later is shippable on a deadline. Debt that silently corrupts a number a customer gets charged for is not shippable unless the corruption is detectable, and detectability is the whole negotiation.
- Make the exposure narrow and measured rather than gestural. The hole is duplicates whose occurrences straddle a UTC day boundary, plus any replay older than partition retention. Measure it before arguing about it: how often an idempotency_key recurs at all, and the distribution of the gap between first and last occurrence. If the ninety-ninth percentile of that gap is four minutes, the residual risk is a small band around midnight and you can say so numerically.
- Add the detector before the feature, not after. A nightly job counting keys that appear in more than one partition is one grouped scan over recent partitions, and it converts a silent overcount into a page. State what it costs to run and what it fires on.
- Buy the cheap half of the real fix immediately: extend partition retention so the dedup horizon exceeds the producer's maximum retry window plus the longest replay you intend to support. That reframes retention as a correctness parameter rather than a storage cost, which is the sentence you need on record before someone optimises the bill.
- Make repayment mechanical instead of aspirational: a dated entry with a named owner, plus a threshold that pulls the date forward — first detector hit above N events, or first customer dispute. Debt with a trigger gets paid; debt with only a date does not.
- Answer the second half honestly by naming what you would refuse under identical pressure: the sealing path, because a sealed row is frozen and a wrong number there stops being a bug and becomes an adjustment line, a dispute and an audit question.
Follow-up
- The detector fires on forty duplicate events for one tenant, and two of their invoices have already sealed. What happens next?
- Whom did you tell that the billing numbers had a known hole, and in what words?
- Finance asks you to cut storage by shortening partition retention. What do you say, and to whom?
Own the incident where invoices undercounted metered usage
A metering consumer acknowledged each batch before committing the fold into usage_rollup_hourly. A rolling deploy restarted consumers mid-batch for two hours; roughly 1.4M usage_event rows were acknowledged and never folded, and 61 invoices sealed against the resulting rollups before anyone noticed. Take the owner's role. Describe an incident of comparable blast radius you owned: how it surfaced, the query that sized the loss, what you stopped first, and how the money was corrected. Give a wall-clock timeline and one thing you got wrong while it was still live.
Approach
- Open with the invariant that broke and the direction of the error, because they determine everything else: acknowledging before committing makes the consumer at-most-once, so this loses events rather than duplicating them, and loss raises no error anywhere. A listener who hears 'we lost revenue silently' knows immediately why detection took two hours.
- Size it with a stated reconciliation rather than an adjective: sum(quantity) from usage_event grouped by (tenant_id, sku, hour of occurred_at) over the window, against usage_rollup_hourly.quantity_sum on the same keys, filtered to environment='production' because staging and sandbox are metered but not billed. Then bisect by hour and tenant until single cells explain the gap. Say how long that ran and whether a replica could serve it while the incident was live.
- Separate mitigation from fix and say which came first. Mitigation is holding the sealing job, because a sealed row is frozen by design and every minute of sealing converts a recoverable rollup into an invoice correction. The fix is moving the acknowledgement after the commit, which re-introduces duplicates that the dedup check on (tenant_id, idempotency_key) must now absorb.
- State the correction path in the domain's own terms: sealed periods are never edited, so each affected tenant gets an adjustment line on the next invoice with kind='adjustment' and voided_by_line_id pointing at the line it reverses, priced against the same rate tier and carrying the watermark it priced against. That is four separate numbers — tenants affected, minor units, the cycle the adjustment lands in, and when customers were told.
- Close on one prevention control with its cost, not five: a per-hour reconciliation comparing raw sum to rollup sum that pages above a threshold. Name the threshold and the false-page rate you accepted, because a detector nobody will keep staffed is not prevention.
- Name a mistake you made inside the response window — the wrong first hypothesis, a mitigation that made it worse — rather than a design mistake from six months earlier. That is the part candidates rehearse away and interviewers weight heavily.
Follow-up
- Your fix moves the acknowledgement after the commit. What breaks now, and what absorbs it?
- One undercharged tenant has since churned. Do you bill them, and who decides?
- How would you have caught this in ten minutes instead of two hours, and what would that detector cost you in pages per week?
- 01
Walk through a complex project from your resume and the technical hurdles you overcame.
- 02
Why do you want to work at Palantir?
- 03
How do you manage trade-offs when project requirements conflict?
- 04
Describe a time your values conflicted with a decision, or you disagreed with leadership. What did you do?
- 05
Reflect on a team experience: what you contributed, and what you would do differently.
- 06
How would you explain your choice between a forward-deployed engineering path and a traditional software engineering role?
Is this an official Palantir interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Palantir. Rounds and questions reflect what candidates have reported, not a process Palantir has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How hard are the Palantir Software Engineer coding questions?
The reported questions look like standard problems (heaps, hash maps, graph traversal, shortest paths), but the source notes say they are often changed or tied to a specific context. Practise implementing classes from scratch, such as a session manager or a road graph, rather than only solving isolated function-style problems. Also practise adapting a known solution when one constraint changes.
PracHub interview research ↗Should I focus more on coding or system design?
Prepare for both. The source notes call coding the primary filter, and the reported questions also include a design of a monitor collecting metrics from 1000 servers every ten minutes, with follow-ups on threading and data consistency. The question bank adds design topics such as employee lookup, grid-based spatial indexing and an in-memory database with transactions.
PracHub interview research ↗What matters most when I get stuck?
How you recover. When a bug or unexpected constraint appears, say what you are checking and why, test a small case by hand, and change your approach openly. The source notes treat this kind of troubleshooting as more important than getting a perfect answer on the first try.
PracHub interview research ↗Which programming language should I use?
The role requirements ask for proficiency in at least one object-oriented language and give Java, C++ and Python as examples. Several reported questions ask you to build classes (a session manager; Location, Road and RoadConnection), so choose the language in which you can write clean classes and tests quickly. Confirm the allowed languages with your recruiter.
PracHub Software Engineer practice ↗How should I prepare for the concurrency questions?
Multithreading and concurrency are listed as must-have skills, and the reported design question asks about threading and data-consistency trade-offs. Practise explaining a race condition on shared state, the fixes (locks, atomic operations, single-writer ownership) and what each fix costs. The question bank's payment race condition problem is good practice for the debugging side.
PracHub Software Engineer practice ↗Will I be asked about my past projects?
Yes. The reported behavioral questions include walking through a complex project and the technical hurdles you overcame, and the source notes say to expect a resume deep dive. Be ready to explain the reason behind every architectural decision in the projects you list.
PracHub Software Engineer practice ↗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