Perplexity answers questions with direct, conversational responses generated by language models, rather than a list of results. In the source notes this guide draws on, the Software Engineer role covers that whole pipeline: low-latency model inference, real-time data ingestion, backend APIs, and the web, mobile and browser interfaces people use to ask questions.
The loop structure is reported to vary only slightly by specialization, but the track-specific questions differ. Candidates describe frontend, mobile (iOS) and infrastructure/AI tracks. The reported track questions include building a React and TypeScript to-do app with nested task dependencies, an iOS component that fetches and parses JSON and updates the UI on the main thread, explaining the JavaScript event loop under high-frequency updates, and cutting model serving latency with PyTorch or TensorFlow. Design questions reported for the role include a real-time chat backend with a SQL-versus-NoSQL choice, a low-level design for a priority task scheduler with retries and dependencies, a rate-limit-aware API client, and a low-latency inference pipeline.
For preparation, that means a shared core plus depth in one track. The core is multi-part coding, where each part adds a requirement to code you already wrote, together with algorithmic optimization and design that starts at architecture and ends in class definitions. Before you commit your week, ask the recruiter which track your loop is for.
Recruiter Conversation
reportedCandidates describe this as an initial conversation about your background, your expectations and your interest in the company. The sources say the later steps may vary slightly by specialization (frontend, mobile or infrastructure), so this call is your best chance to learn which track your loop is for and whether the technical screening will be an online assessment or a live machine coding session. The sources also say recruiters often mention a high-intensity, startup-style work pace, so have a real answer ready about how you manage your energy and priorities.
What to demonstrate
- How well your background fits the role and the track you are being considered for
- Your expectations, including start date, location and compensation range
- Your interest in the company and whether you understand what the product does
How to prepare
- Ask directly which track the loop is for and whether the screen is an online assessment or live machine coding, then plan the rest of your week around the answer
- Use the product before the call and prepare two sentences on which part of the pipeline you want to work on (inference, ingestion, APIs or clients) and why your background fits it
- Prepare a concrete example of how you kept quality up while priorities shifted, since work pace is reported to come up
- Write down your constraints and a compensation range backed by current data points, and state them as facts
Technical Screening
reportedThe sources describe this step as a challenging technical assessment: either an online assessment or a live machine coding session, often on CoderPad. Candidates report that the online assessment is a major filter. Passing every visible test case is not enough if the code fails the optimization tests, lacks clean structure or ignores basic engineering practice. Machine coding problems come in sequential parts, and each new requirement builds on the code you already wrote. Get a correct baseline working first, then optimize it, and keep the structure easy to extend so the next part does not force a rewrite.
What to demonstrate
- Whether your solution passes performance tests on large inputs, not only the visible correctness cases
- Whether your code structure survives the next part's added requirement without a rewrite
- Whether you can state time and space complexity and improve a working baseline
- Whether you debug a failing case systematically rather than editing at random
How to prepare
- Practise multi-part problems from the bank that fit this format, such as a dependency-aware to-do list, task dependencies with failure handling, an in-memory file system and a time-versioned key-value store with restore, adding a new requirement after each part works
- After every passing solution, write a maximum-size input that would break a quadratic version and run it
- Drill the reported algorithm families: a sliding window with rolling statistics over a token stream under a memory limit, and detecting and reporting a cyclic dependency
- Rehearse in a plain editor without autocomplete so that looking up standard-library calls does not slow you down
Virtual Onsite Day
reportedCandidates who pass the screens are invited to a virtual onsite day with multiple specialized rounds. The sources do not give the exact list of rounds, but they report a heavy emphasis on live coding and system design, adjusted to your specialization. They describe the design discussions as starting with system-level trade-offs, such as database choice, caching and network protocols, before moving into the class interfaces and data structures that would implement the system. Prepare to explain how your own stack works underneath, not only its APIs.
What to demonstrate
- Whether you can justify a database and architecture choice from access patterns and then turn it into concrete classes
- Whether you understand how your track's tools work underneath: React and TypeScript rendering, Swift concurrency, or serving through PyTorch and TensorFlow
- Whether you explain trade-offs out loud as you design instead of presenting a finished diagram
- Whether your class interfaces stay extensible when a requirement changes partway through the problem
How to prepare
- Pick two of the reported design questions, whichever rounds they end up in, such as the chat backend with SQL versus NoSQL and the priority task scheduler, and practise one answer that runs from the storage choice to class definitions
- Go deep on your track: the event loop and re-render control for frontend, main-thread UI updates and thread-safe networking for iOS, or batching and latency budgets for inference serving
- Read up on retrieval-augmented generation, vector databases and LLM inference pipelines, which the sources recommend as background
- Practise adding a requirement mid-design, such as a new priority rule or a retry policy for the scheduler, and check whether your classes absorb it without a rewrite
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
Perplexity Data Scientist Interview Experience — A Stratified Sampling Coding Screen, Then a Next-Day Rejection
View report detailsPerplexity Software Engineer Interview Experience — A 4-Part 'Todo List for AI' Coding Round
I applied through LinkedIn. The first round was with a recruiter who just asked some basic info, then scheduled a coding round. It had to be done in Python. The coding round question was the "implement todo list for AI" one that's already been posted on the forum. There were 4 parts total. I only got through the first two parts before running out of time. The second part was a pain because every…
Read full experiencePracHub editorial advice for the preparation topics above.
Treating the online assessment as finished once the visible test cases pass
Candidates report being rejected for code that passed every visible case but failed the optimization tests or looked unstructured. Before you submit, state the complexity out loud, build the largest input the constraints allow and run it, and replace any nested scan over the input with a hash map, heap or sliding window where one fits. Leave a minute to rename variables and pull repeated logic into functions, since structure is reported to count too.
Writing part one of a machine coding problem so tightly that part two forces a rewrite
These problems add requirements in stages. Reported scenarios include a to-do list manager with undo/redo and hierarchical subtasks, and a dependency-graph task runner that must handle cycles. From the first part, keep state in a small class with explicit methods, store the dependency graph apart from the display logic, and ask which kinds of extension are likely before you pick data structures. A correct, extensible baseline that you then optimize beats a clever single function you have to throw away.
Detecting a dependency cycle but not being able to say which jobs form it
Kahn's algorithm tells you a cycle exists when fewer than V nodes come out, but it does not name the cycle. The leftover nodes with nonzero indegree contain every cycle, so run a three-colour DFS on that leftover subgraph and report the stack segment from the grey node the back edge points to. Practise on the dependency-graph drill in this guide until you can write both halves without notes, because configuration and scheduler questions about cyclic dependencies are reported for this role.
Answering SQL versus NoSQL for the chat backend with a label instead of access patterns
Start from the operations. Messages are appended per conversation, read newest-first in pages, and must stay in order within a conversation. From that, choose a key such as conversation id plus a per-conversation sequence number, say what consistency you need across devices, and name the query your choice makes expensive, such as searching across all conversations. Then write the core classes and interfaces, because the design discussion is reported to go from trade-offs down to code.
Answering the track-specific questions with definitions instead of mechanics
For the event loop, explain the order: each task runs to completion, then the microtask queue (promise callbacks) drains completely, and only then can the browser render, so a long task or a chain of microtasks delays paint. Then say what you would do about it: batch high-frequency updates into one requestAnimationFrame, and memoize components so unchanged subtrees do not re-render. Prepare the equivalent level of detail for iOS (network fetch and JSON parsing off the main thread, UI updates dispatched back to the main thread) or for inference serving (batching, and where latency is actually spent), depending on your track.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Fold a deduplicated usage stream into hourly rollups
You are given one day of usage_event rows, up to 250 million, each carrying event_id, tenant_id, workspace_id, environment, sku, quantity numeric(20,6), idempotency_key, occurred_at and ingested_at. Produce usage_rollup_hourly cells keyed (tenant_id, workspace_id, sku, hour_start) with quantity_sum, event_count and source_max_ingested_at. An event counts once per (tenant_id, idempotency_key). The rollup grain has no environment column, so state your filter. One pass. Give your time and space bounds, and say what the deduplication actually costs in memory.
Approach
- Bucket on
occurred_at, neveringested_at:hour_start = date_trunc('hour', occurred_at at time zone 'UTC'). The two columns answer different questions.occurred_atsays which hour the customer is billed for;ingested_atsays how current the fold is. Using the second for the first makes late data invisible instead of correctable. - The fold is trivial and the deduplication is the entire cost, so price it before designing anything clever. An exact set over
(tenant_id, idempotency_key)at 250M entries, stored as a 16-byte 128-bit hash in an open-addressed table at 0.7 load factor, needs about 357M slots at 16 bytes each, roughly 5.7 GB. The fix is partitioning byhash(tenant_id) % Pso each shard holds 1/P of the set and no tenant's keys straddle shards. - Rule out a Bloom filter as a replacement, in the right direction: a false positive reports 'already seen' for an event never seen, so you drop a real event and lose revenue with no error raised. It is usable only as a negative pre-filter in front of the exact set, where a miss is conclusive and a hit must fall through to the real lookup.
- Accumulate in scaled integers, not binary floating point.
numeric(20,6)admits values below 10^14, so one event scaled to micro-units can reach 10^20, past int64's 9.22 x 10^18; use a 128-bit or arbitrary-precision accumulator unless you first bound the per-event maximum. binary64 represents integers exactly only to 2^53, about 9.01 x 10^15, and cannot represent 0.1 at all, so two runs that sum in different orders disagree. - Carry
source_max_ingested_at = max(ingested_at)over the events folded into each cell, and countevent_countover accepted, post-dedup events. Without that watermark there is no way to prove later what a number did and did not include, which is the first question any reconciliation asks. - State the environment filter explicitly, because the rollup grain cannot record it. A fold that quietly includes
stagingbills non-production traffic; one that quietly excludes it loses a cost signal. Production-only is the billing answer, and either way it belongs in the job name and the output metadata. Complexity: O(n) time, O(distinct dedup keys) space, dominated by the dedup set rather than by the cells.
Worked solution 25 min
- Write both key tuples down before any code: dedup key
(tenant_id, idempotency_key), cell key(tenant_id, workspace_id, sku, hour_start), withhour_startderived fromoccurred_atin UTC. - Build a 10,000-row fixture containing one event duplicated three times under the same
idempotency_key, two events sharing anidempotency_keyacross differenttenant_idvalues, one event whoseoccurred_atis two hours before itsingested_at, and onestagingevent inside an otherwise production cell. - Fold it and assert each of those four expectations separately rather than eyeballing a grand total.
- Re-run with the input shuffled and diff the output files.
- Size the dedup set for 250M keys using the load-factor arithmetic and write the number down next to the fixture.
Follow-up
- A producer retries at 23:59:59 and the retry lands at 00:00:01. The unique index on the daily-partitioned table must include the partition key. What gets double-counted, and what is the smallest change that fixes it?
- The consumer acknowledges its batch before committing the fold. Which failure loses revenue now, and which arrangement duplicates instead?
- What makes a re-run over the same day produce byte-identical rollups?
Schedule ordered webhook retries with a heap of subscription queues
Design the in-memory scheduler for webhook delivery. Up to 20 million rows sit in status pending or failed_retryable across 200,000 subscriptions, each row carrying next_attempt_at and attempt_count, and each endpoint having a circuit breaker. Deliveries for one subscription must be attempted in order, so at most one attempt per subscription may be in flight. Support due(now), complete(delivery, outcome) and insert(delivery) in O(log S), where S is the subscription count rather than the delivery count. Give the backoff formula you schedule retries with.
Approach
- Key the global heap by subscription, not by delivery. Each subscription owns a FIFO of its due deliveries in event order; the heap holds one entry per eligible subscription, keyed by its head's
next_attempt_at. That is 200,000 heap entries instead of 20 million, and it makes the one-in-flight rule structural rather than a check somebody can forget. due(now): peek the minimum. If its key is in the future, sleep until then instead of spinning. Otherwise pop it, move the subscription into an in-flight set, and do not re-push it. A subscription absent from the heap cannot be dispatched twice, which is precisely how ordering is preserved.complete: on success, drop the head and re-push the subscription keyed by its new head, or leave it out when the queue empties. On a retryable failure, incrementattempt_countand setnext_attempt_at = now + uniform(0, min(cap, base * 2^attempt)), sampled uniformly across the whole interval. That is full jitter; deterministic backoff re-synchronises the herd you just created.- Circuit breaker: park the subscription in a second heap keyed by its half-open time, so an endpoint dead for six hours costs one heap entry and zero attempts rather than consuming worker slots. Admit exactly one probe at half-open and close the breaker only on its success.
- Say the price of the ordering guarantee out loud. One in-flight attempt per subscription means an endpoint answering in 10 seconds drains at 0.1 deliveries/second however many workers you run, and its backlog grows until it recovers. If the customer does not need order, allow k in flight and document delivery as unordered; that is the trade, and it is a product decision.
- All three operations are O(log S) with O(S) resident heap memory and the queues themselves backed by the store. The database-backed equivalent is a partial index on
(subscription_id, next_attempt_at) where status in ('pending','failed_retryable')claimed withFOR UPDATE SKIP LOCKED, and the write-back must be fenced onlease_tokenso a worker that stalled and resumed cannot overwrite a newer attempt.
Follow-up
- One subscription has 4 million queued deliveries. What stops it from starving the other 199,999, and what does your heap look like under that load?
- A customer requests redelivery of last Tuesday's events. Where do those rows enter your structure, and what keeps them from reordering live traffic?
- The process restarts. How much state do you rebuild, and what stops every subscription from being attempted in the same second?
Order a job dependency graph and find its critical path
A workspace defines up to 50,000 jobs with up to 200,000 dependency edges and an estimated duration_seconds per job. Given the edge list, reject the graph if it contains a cycle and name one cycle's nodes; otherwise return a valid execution order, the earliest possible completion time with unlimited workers, and the set of jobs whose slack is zero. Then say which single job to shorten in order to cut the completion time, and by exactly how much. State the complexity of each part.
Approach
- Kahn's algorithm for the order: compute indegrees, seed a queue with zero-indegree nodes, emit and decrement. O(V + E), which at 50,000 and 200,000 is milliseconds. If fewer than V nodes are emitted, the graph contains a cycle.
- Kahn detects a cycle but cannot name one. The nodes left with indegree above zero contain every cycle, so run one DFS restricted to that residual subgraph with three-colour marking and report the stack slice from the grey node the back edge points at. That is the difference between a usable error message and 'dependency cycle detected'.
- Earliest completion with unlimited workers is the longest path, which is NP-hard on a general graph and linear on a DAG. State the precondition, then relax in topological order:
earliest_finish[v] = duration[v] + max(earliest_finish[u] for u in preds(v)), taking the max over an empty predecessor set as zero. The makespan T is the maximum over all nodes. O(V + E). - Second pass in reverse topological order for
latest_finish, thenslack[v] = latest_finish[v] - earliest_finish[v]. Zero-slack nodes form the critical path, and there can be several disjoint critical paths, so return the set rather than one chain.slack[v] = 0is exactly the statement that some longest path runs through v; equivalently, the longest path through v has lengthT - slack[v]. - The speed-up bound is the point of the question, and the obvious form of it is wrong. Shortening a zero-slack job v by d, with 0 <= d <= duration[v], cuts the makespan by
min(d, T - L_avoid(v)), whereL_avoid(v)is the longest path in the graph with v deleted: the longest path that avoids v, not the second-longest path overall. The two coincide only when the runner-up path misses v. Counterexample: A of 10 s feeds both B of 5 s and C of 4 s, so T = 15 s and the second-longest path is 14 s, yet shortening A by 10 s leaves a makespan of 5 s. The realised gain is the full 10 s, because both paths ran through A and shrank together, whilemin(10, 15 - 14)predicts 1 s. The reason is structural: shortening v reduces every path through v by d and leaves every other path alone, so the new makespan ismax(T - d, L_avoid(v)). - Compute
L_avoid(v)the direct way: delete v and re-run the same forward relaxation, O(V + E) per candidate. The cheaper equivalent skips the deletion, sinceL_avoid(v)only ever matters through that max: setduration[v] := 0, recompute the makespan asT0(v) = max(T - duration[v], L_avoid(v)), and the gain ismin(d, T - T0(v)), which is identical for every d <= duration[v]. Only zero-slack jobs are candidates, because shortening a job with positive slack changes the completion time not at all. One relaxation is milliseconds at this size, so ranking a critical set in the hundreds costs O(k(V + E)) and is worth doing exactly; a critical set in the tens of thousands is not, and there you evaluate a shortlist, longest jobs first, and say that the answer is the best of that shortlist rather than the optimum.
Follow-up
- Only m workers are available. What happens to your answer, and what can you still promise about the schedule you produce?
- Edges arrive incrementally as the customer edits the pipeline. How do you detect a cycle at insert time without re-running Kahn over 250,000 elements?
- Durations are estimates. How would you express completion time as a distribution, and what breaks about the critical path once you do?
Explain why the metering dashboard scans every daily partition
usage_event is range-partitioned daily on ingested_at and holds tenant_id, workspace_id, environment, sku, quantity numeric(20,6), occurred_at and ingested_at. The only relevant index is on (occurred_at). A dashboard runs select sku, sum(quantity) from usage_event where tenant_id = $1 and date_trunc('hour', occurred_at) >= $2 and environment = 'production' group by sku, and EXPLAIN shows a sequential scan of every partition. Give each distinct reason, rewrite the predicate so an index can serve it, propose the index, and state the write cost its column order adds.
Approach
- Separate the three causes rather than blaming one. First,
date_trunc('hour', occurred_at)wraps the column, so the predicate is not sargable against a btree on the bare column. Second, pruning keys off ingested_at while the query constrains occurred_at, so no partition can be excluded. Third, even made sargable, (occurred_at) is not tenant-leading, so for one tenant among thousands the scan reads the whole time range and discards nearly all of it. - Rewrite the bound carefully, because the obvious rewrite is only conditionally equivalent.
date_trunc('hour', x) >= $2equalsx >= $2only when $2 is already hour-aligned; for an arbitrary $2 it meansx >= date_trunc('hour', $2) + interval '1 hour'. Normalise the parameter in the caller and leave the column bare. - Restore pruning with a second, redundant predicate on the partition key:
ingested_at >= $2 - interval '<late-data horizon>'. State both sides of it. It prunes to a handful of partitions, and it silently omits any event whose ingest lagged past that horizon, which is precisely what a producer replay produces. Either document the horizon as a stated bound, or partition on occurred_at and move the problem into the dedup window instead. - Propose
(tenant_id, occurred_at) include (sku, quantity)per partition. A partial indexwhere environment = 'production'mostly saves size rather than selectivity, since production dominates the three environments; take it if non-production is a meaningful share and skip it otherwise. - Price the write path honestly. At roughly 250M rows/day each extra index is another insert plus WAL per row, and a tenant-leading key scatters inserts across one hot leaf per active tenant instead of appending to a single rightmost leaf, so page dirtying and random I/O both rise. An INCLUDE payload widens every leaf entry and enlarges the index accordingly.
- Add the index-only-scan caveat before someone reports it as a regression: on a freshly appended table the visibility map is not yet set for recent pages, so the INCLUDE columns still cost heap fetches until autovacuum has been through, and the newest hour is exactly the data the dashboard reads.
Worked solution 30 min
- Build 30 daily partitions with skewed tenants, one holding about 40% of the rows, then ANALYZE.
- Run
explain (analyze, buffers)on the original query and record how many partitions were scanned and the rows removed by filter. - Apply the rewritten predicate and the index, re-run, and confirm the plan lists only the partitions inside the ingested_at bound.
- Re-run with $2 set to a non-hour-aligned timestamp and confirm the rewritten and original predicates return identical rows.
- Insert an event with ingested_at six hours past occurred_at and check whether the pruning predicate excludes it.
Follow-up
- CREATE INDEX CONCURRENTLY is not supported on a partitioned parent. Give the sequence that gets this index onto 400 existing partitions without blocking ingest.
- One tenant holds 200 times the median row count and the dashboard still times out for them with the index in place. What changes?
- Should this read hit
usage_rollup_hourlyinstead? State what that costs in freshness and what the watermark lets you promise.
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.
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?
How would you design a scalable API client that reads data from a high…
How would you design a scalable API client that reads data from a high-throughput endpoint while gracefully managing rate limits and network partitions?
Approach
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Build a dynamic to-do list application in React and TypeScript that su…
Build a dynamic to-do list application in React and TypeScript that supports state changes, nested task dependencies, and custom rendering rules.
Approach
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
- 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?
Explain the event loop in JavaScript and how asynchronous operations c…
Explain the event loop in JavaScript and how asynchronous operations can impact UI rendering performance in high-frequency data applications.
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?
Metering ingest that survives a six-hour producer replay
metering-ingest consumes usage events at-least-once - 250M/day, 10-40k/second at peak - and folds them into usage_rollup_hourly keyed (tenant_id, workspace_id, sku, hour_start). usage_event is partitioned daily on ingested_at with unique (ingested_day, tenant_id, idempotency_key). A producer outage ends in a six-hour replay that re-sends events already ingested, some of whose originals crossed midnight. Design the consumer: partitioning, where the acknowledgement sits relative to the commit, the deduplication horizon and its storage cost, and how the rollup watermark advances. Nothing may be double-counted and nothing may be silently dropped.
Approach
- Choose the acknowledgement position deliberately and name what each choice costs. Acknowledging after the fold commits makes the consumer at-least-once: a crash between the two replays the batch and produces duplicates, which are ordinary and absorbable. Acknowledging first makes it at-most-once: a crash between the two drops revenue with no error raised anywhere and no way to detect it later. Take at-least-once and design everything downstream to absorb duplicates.
- Put the dedup and the fold in one transaction so there is no window between them. Insert the batch into usage_event with ON CONFLICT DO NOTHING, take the rows actually inserted, and fold only those into usage_rollup_hourly with an upsert on (tenant_id, workspace_id, sku, hour_start) bucketed by occurred_at, not ingested_at. A batch of about 2,000 rows is one round trip and one index probe per event.
- Attack the partition-key flaw head on: the unique index includes ingested_day because a unique index on a partitioned table must contain the partition key, so the same (tenant_id, idempotency_key) re-sent after midnight is a different index entry and passes. Deduplicate instead against a store keyed (tenant_id, idempotency_key) with no date component, whose horizon exceeds the producer's maximum retry window plus the longest replay you intend to support. At 14 days that is 250M x 14 = 3.5 billion keys, which is a dedicated key-value store, not a larger index on the same table. The alternative - partitioning usage_event on (tenant_id, occurred_day) so the natural key is stable - fixes dedup but loses pruning on ingest time and makes retention by dropping partitions awkward.
- Partition the consumer by hash of tenant_id so one tenant's replay stalls only its own partitions, and give replay traffic a separate lower-priority lane so live ingest keeps its latency. The cost is explicit: that tenant's watermark lags while the replay drains, and everything gated on the watermark waits for it.
- Define the watermark as a property of committed work, not of wall-clock time: per partition it is the largest occurred_at such that every event with a smaller occurred_at has committed, and the sealing decision uses the minimum across partitions. Record source_max_ingested_at on every rollup row so any number can prove what it did and did not include, and keep restatement legal only while status = 'open' - after sealed_at the value is frozen and a late event becomes an invoice adjustment instead.
Worked solution 40 min
- Write the consumer loop in pseudocode with the acknowledgement after the commit, then annotate each line with what is lost or duplicated if the process dies exactly there.
- Size the dedup store: events/day x horizon_days keys, bytes per key including the tenant prefix, and the resulting memory or disk. Compare that cost against simply extending retention on the partitioned table and say why the latter does not fix the problem.
- Take one event ingested at 23:59:58 and replayed at 00:00:04 and work out its fate under (a) the partitioned unique index alone and (b) the separate dedup store.
- Write the per-partition watermark formula, then what the seal uses, then what a single stalled partition does to sealing.
Follow-up
- The dedup store is lost entirely. What can you still guarantee, and how do you rebuild it from what remains?
- A replay delivers events for an hour that is already sealed. Trace exactly what happens to them, row by row.
- One partition is stuck on a poison message, so the minimum-across-partitions watermark never advances and no tenant can be sealed. What is your escape hatch and what does it cost in correctness?
One tenant's counter writes stall the whole connection pool
A change that made a per-tenant usage counter correct now produces site-wide latency whenever one large tenant writes: unrelated endpoints time out waiting for a connection while database CPU stays low and no statement is slow. The change wraps the counter update in a transaction that takes SELECT ... FOR UPDATE on one row, calls an external pricing service, then updates and commits. Give an ordered checklist, the arithmetic that bounds that tenant's write rate, and three repairs with the cost each one accepts.
Approach
- Separate waiting from working. Low database CPU alongside high application latency points at a queue, so instrument connection-acquisition wait separately from query execution time; that queue forms in the application and is invisible in database metrics, which is why the database looks healthy throughout.
- Confirm the lock rather than assuming it: sample waiting sessions and group by wait event, relation and tuple. Contention concentrated on one tuple belonging to one tenant is the signature; a deadlock would instead show the database aborting transactions after its detection timeout, which is not happening here.
- Do the arithmetic out loud. Throughput on a serialised row is one divided by the lock hold time, and the hold spans the external call, so a 20 ms pricing call caps that tenant near 50 writes per second no matter how many pods run. Every waiter also holds a pooled connection while it queues, so the shared pool drains and unrelated tenants fail at acquisition.
- Repair one: shrink the critical section to a single statement with the price resolved before the transaction opens. Cost is a stale price for the duration of one request and a second round trip; benefit is a hold time measured in the database's own execution time.
- Repairs two and three change where the contention lives rather than how long it is held. Sharding the counter into per-(tenant, bucket) rows and summing on read multiplies write throughput by the shard count, at the cost of an aggregate on every read and a shard count you must size against the largest tenant rather than the median. Accumulating in memory and flushing periodically removes the per-write round trip entirely, paid for with a bounded loss window on crash, which is acceptable for a rate limiter and not for a billing counter.
- Contain independently of which repair wins: a separate pool or per-tenant concurrency cap for this write class, a statement timeout low enough that a pathological query dies before it accumulates waiters, and an idle-in-transaction timeout so a stuck client cannot pin a connection and its locks.
Follow-up
- What would a genuine deadlock look like here, which two code paths would produce one, and how does the database's response differ from what you observed?
- If a transaction-pooling proxy sits in front of the database, which of your three repairs changes behaviour, and what stops working that would have worked on a direct connection?
- The counter also enforces a quota. Why is SELECT the count and then INSERT still wrong after you have fixed the contention?
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Map the loop and pick your track
- Write down the three reported stages (recruiter conversation, technical screening, virtual onsite day) and what you need to learn from the recruiter: which track, and whether the screen is an online assessment or live machine coding.
- Sort this guide's questions into algorithmic coding, machine coding, design and LLD, track-specific, SQL, debugging and behavioral, and mark the categories you have never practised.
- Prepare your recruiter answers: why this product, which part of the pipeline you want to work on, how you manage priorities at a demanding pace, and your constraints and range.
Deliverable: A one-page map of categories marked by confidence, a stated track, and recruiter answers written down.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Algorithmic coding against optimization tests
- Solve a sliding-window problem computing rolling statistics over a stream under a memory limit, then state its complexity and why it stays within that limit.
- Work the drill 'Order a job dependency graph and find its critical path': Kahn's order, naming one cycle, earliest completion time and zero-slack jobs.
- For each solution, generate a maximum-size input and run it; if a hidden performance test would fail, rewrite before moving on.
- Work through the worked exercise 'Fold a deduplicated usage stream into hourly rollups' and check your memory estimate against its dedup-set arithmetic.
Deliverable: Two solutions with stated complexity, each passing a self-built maximum-size test, plus the cycle-reporting code saved for reuse.
Practice prompt ↗Practice prompt ↗03Incremental machine coding
- Build a dependency-aware to-do list in stages you set yourself (for example add and complete tasks, then hierarchical subtasks, then undo/redo), adding each stage only after the previous one passes its tests.
- Build an in-memory file system or a time-versioned key-value store with restore in the same staged way, writing the tests for each part before the code.
- After each stage, note any change that forced you to rewrite earlier code and what structure would have avoided it.
- Practise in a plain editor to match a CoderPad-style environment.
Deliverable: Two staged implementations with tests, and a short list of the design decisions that made later stages easy or hard.
Practice prompt ↗Practice prompt ↗04System design and low-level design
- Design the real-time chat backend: access patterns, SQL versus NoSQL with the reasoning, message ordering and sync across devices, then the core class definitions.
- Write the low-level design for a priority task scheduler with retries and execution dependencies, then compare it with the drill 'Schedule ordered webhook retries with a heap of subscription queues'.
- Answer the reported API client question: throughput, rate limits, retries with jittered backoff, and behaviour during a network partition.
- Work through the worked exercise 'Metering ingest that survives a six-hour producer replay' to practise where acknowledgements go and how deduplication works.
Deliverable: Two designs that each end in class interfaces, plus a written rate-limit and partition strategy for the API client.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Track depth
- Frontend: explain the event loop with microtask and rendering order, then build a React and TypeScript component that renders API data without unnecessary re-renders.
- iOS: implement a component that fetches JSON from a REST endpoint, parses it off the main thread and updates the UI on the main thread.
- Infrastructure/AI: prepare the reported question on cutting model serving latency with PyTorch or TensorFlow, and pair it with two reported questions from other categories: the low-latency inference pipeline (design) and reducing tokenization CPU overhead (algorithmic).
- Read up on retrieval-augmented generation, vector databases and LLM inference pipelines so you can connect your design answers to how the product works.
Deliverable: One finished exercise in your own track and written answers to that track's reported questions.
Practice prompt ↗Practice prompt ↗06Data layer and debugging
- Work through the worked exercise 'Explain why the metering dashboard scans every daily partition' and name its three causes separately.
- Write the constraints and lookup query for the drill 'Model credential revocation so history survives the delete'.
- Work the debugging drill 'One tenant's counter writes stall the whole connection pool', writing the ordered checklist before any fix.
- Write a one-page SQL versus NoSQL comparison for message storage that you can recite in a design discussion.
Deliverable: A written query rewrite with its index, a debugging checklist, and the SQL versus NoSQL page.
Practice prompt ↗Practice prompt ↗07Behavioral stories and a mock loop
- Write stories for the three reported behavioral prompts: a trade-off under a tight deadline, staying productive while product direction shifts, and a production bug you diagnosed and prevented from recurring.
- Cut every story to a decision you made, the evidence behind it and a measured outcome, and rewrite any sentence where 'we' hides what you personally did.
- Run a mock: a multi-part coding problem with a requirement added partway through, followed right away by one design question from day four.
- Write down where your structure broke under the added requirement or where the design answer stayed at the whiteboard-box level.
Deliverable: Three rehearsed behavioral stories and notes from the mock loop listing the fixes to make before the real interviews.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The reported behavioral prompts for this role are about trade-offs under deadline, keeping quality up while product direction shifts, and production debugging. The sources also say recruiters often mention a demanding work pace. Build each answer around a decision you made yourself: what you knew at the time, what you chose to cut or defer, and a number that shows the result. Say what you would change, and be specific.
How do you manage your productivity and maintain high-quality engineer…
How do you manage your productivity and maintain high-quality engineering standards in an environment characterized by rapid shifts in product direction?
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Walk through a complex technical bug you encountered in production. Ho…
Walk through a complex technical bug you encountered in production. How did you diagnose it, and what long-term preventive measures did you implement?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Describe a time when you had to make a critical technical trade-off un…
Describe a time when you had to make a critical technical trade-off under a tight deadline. What was the outcome, and what did you learn?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
- 01
Describe a time when you had to make a critical technical trade-off under a tight deadline. What was the outcome, and what did you learn?
- 02
How do you manage your productivity and maintain high-quality engineering standards in an environment characterized by rapid shifts in product direction?
- 03
Walk through a complex technical bug you encountered in production. How did you diagnose it, and what long-term preventive measures did you implement?
- 04
Tell me about your past experience.
- 05
How do you manage your energy and prioritize tasks while keeping output quality high in a fast-paced environment?
Is this an official Perplexity interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Perplexity. Rounds and questions reflect what candidates have reported, not a process Perplexity has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What rounds should I expect in the Perplexity Software Engineer loop?
Candidates describe three stages: a recruiter conversation, a technical screening that may be an online assessment or a live machine coding session, and a virtual onsite day with multiple specialized rounds. The steps may vary slightly by specialization (frontend, mobile or infrastructure), so ask your recruiter which track and screening format apply to you.
PracHub Software Engineer practice ↗What makes the technical screening hard?
Candidates report that the online assessment is a major filter. Code that passes every visible test case can still be rejected if it fails the optimization tests or lacks clean structure. Machine coding problems are multi-part and add requirements as you go. Prepare by getting a correct baseline working, testing it on maximum-size inputs, and keeping the structure easy to extend.
PracHub interview research ↗How long does the process take?
Candidate reports put it at roughly three to five weeks from the recruiter conversation to a decision. The sources also say it can move faster, in as little as two weeks, or run longer depending on scheduling and how much preparation time you ask for. If you have a competing deadline, tell the recruiter early.
PracHub interview research ↗Will I get feedback after my interviews?
Candidates report that updates on whether you are moving forward can come quickly, sometimes the same day as an assessment, but detailed feedback on technical performance is generally not shared. Keep your own notes after each round so you have something to learn from either way.
PracHub interview research ↗What should I expect to discuss about work pace?
The sources say recruiters often describe a high-intensity, startup-style work pace that can involve long hours. Be ready to explain how you manage your energy, prioritize tasks and keep quality up when priorities change, and use a specific example rather than a general statement.
PracHub interview research ↗Do the questions differ by team?
Yes, according to candidate reports. Frontend candidates report React and TypeScript builds and questions about the JavaScript event loop. iOS candidates report a component that fetches and parses JSON and updates the UI on the main thread. For infrastructure, the reported track question is optimizing model serving latency with PyTorch or TensorFlow. Separately, reported algorithmic and design questions include reducing tokenization CPU overhead and a low-latency inference pipeline. Algorithmic coding and system design questions are reported whatever your track.
PracHub Software Engineer practice ↗Do I need machine learning knowledge for a Software Engineer role?
Reported questions include optimizing tokenization, serving latency with PyTorch or TensorFlow, and a low-latency inference platform, and the sources recommend knowing how retrieval-augmented generation, vector databases and LLM inference pipelines work at a high level. How deep you need to go depends on your track. If you are on the infrastructure track, prepare to explain where latency is spent in serving.
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