CPP Investments | Investissements RPC · Software Engineer
Updated · 2026-09-24

CPP Investments | Investissements RPC Software Engineer
Interview Questions & Guide 2026

THE 60-SECOND BRIEF

As a Software Engineer at CPP Investments | Investissements RPC, you are not just writing code; you are building the technological infrastructure that supports one of the world’s most sophisticated institutional investors. Your work directly impacts the management of the Canada Pension Plan Fund, ensuring that millions of Canadians have a secure financial future. Whether you are working on Total Fund Management (TFM), Risk Analytics, or AI & Emerging Technologies, your contributions underpin high-stakes financial decisions that require extreme precision, reliability, and scale.

Ask how many rounds there are and what each one is before you plan your weeks, because the composition is what your hours should follow. A loop with three coding rounds and one short design conversation deserves a different split from the reverse, and whoever schedules it will usually say plainly which it is.

CPP Investments | Investissements RPC candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Name the isolation level each invariant requiresStore money as integer minor unitsReconcile the ledger against processor settlement files

44 min read

Browse Software Engineer questions

See the practice prompts

14Practice promptsAcross five skill areas
3With worked solutionsIncluded in the practice prompts

As a Software Engineer at CPP Investments | Investissements RPC, you are not just writing code; you are building the technological infrastructure that supports one of the world’s most sophisticated institutional investors. Your work directly impacts the management of the Canada Pension Plan Fund, ensuring that millions of Canadians have a secure financial future. Whether you are working on Total Fund Management (TFM), Risk Analytics, or AI & Emerging Technologies, your contributions underpin high-stakes financial decisions that require extreme precision, reliability, and scale.

The environment is intellectually demanding and highly collaborative. You will engage with complex financial models, large-scale data pipelines, and cutting-edge machine learning applications. Success in this role requires a unique blend of technical mastery and an interest in the financial domain. You will work alongside investment professionals to translate complex business requirements into elegant, performant, and scalable software solutions.

01

Technical Screening

reported

What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.

What to demonstrate

  • Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
  • Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
  • Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly

How to prepare

  • Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
  • Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
  • Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live
PracHub interview research ↗
02

System Design Interview

reported

You cannot drill a format you do not know, so put the preparation into material that travels. Three pieces of your own work, each rehearsed until you can take a follow-up you did not anticipate, will carry a conversation or a code walkthrough equally well. Specificity is what separates that from filler. A number needs its definition before it means anything: a p99 is over some window and measured at some hop, and a server-side figure excludes the queueing and network time a client would see. The number you cannot qualify is the one to leave out.

What to demonstrate

  • Whether your examples carry detail only someone who did the work would hold, such as what the binding constraint actually was, which alternative you rejected and why it was worse, and what you measured on each side of the change
  • Whether a number survives one follow-up, meaning you can say what it was measured over and whether it moved because of your change or merely alongside it
  • Whether a failure is described with the specific change that followed it, rather than a lesson stated in general terms
  • Whether your part in a team effort is stated accurately, including what other people did

How to prepare

  • Write a page on each of three projects covering the constraint, the option you rejected, the measurement before and after, and what went wrong. Cut any line you cannot take a follow-up on, since you are writing the parts you will be pressed on rather than a summary.
  • Recover the real figures while you still have access: request volume, data size, latency with its percentile and window, team size, timeline. Note where each came from, whether a dashboard, a design document or memory, and mark the estimates so you can say which they are out loud.
  • Take your weakest project story to someone who works in a different area and have them ask why four times in succession. The point where you run out of answer is the part to go and re-read before the round.
PracHub interview research ↗
03

Deep-Dive Technical Discussions

reported

The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.

What to demonstrate

  • In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
  • In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
  • Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes

How to prepare

  • Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
  • Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
  • For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
PracHub interview research ↗
04

Behavioral Assessments

reported

Many of these questions are about something that went wrong, and the grading sits mostly in the hours after you knew. Who found out first, whether that was you or an alert or a user, how long it took you to say it out loud, and whether the people who needed the news got it while they could still act on it. Engineers under-tell this part because it feels like confessing. The pattern it is looking for is the opposite: the quiet fix, an incident absorbed without telling anyone, after which nothing changed and the same failure is still available.

What to demonstrate

  • How the problem was found, and whether that route was one you had built or one that happened to you, since a user reporting it first means your instrumentation did not cover that failure
  • Whether time-to-detect and time-to-tell are separate numbers in your account and whether you know both, because a fast fix that nobody heard about until the retro is a different answer from a slow one that was announced immediately
  • Whether the resolution left something durable behind, a check that fires or a default that changed, rather than depending on people remembering to be careful
  • Whether you can say what the failure cost without either inflating it or waving it away

How to prepare

  • Reconstruct one incident you were part of as a timeline with clock times: first bad request, first signal, first person who knew, first message outside the team, mitigation, permanent fix. The gaps between those entries are what gets asked about
  • Look up the configuration of the signal that caught it, including its evaluation window and threshold. An alert defined on a five-minute aggregate cannot fire until the condition holds across that window, which puts a floor under time-to-detect that has nothing to do with how severe the failure was. Be able to say what that floor was and whether anyone had chosen it deliberately
  • Prepare one story where you escalated early and the severity turned out to be smaller than you thought, including what it cost the people you pulled in. Without it, every answer you give about raising alarms is unfalsifiable
PracHub interview research ↗

PracHub editorial advice for the preparation topics above.

01

Deriving the business date from the UTC timestamp

Posting date, value date and the processor's settlement date are three different dates, determined by cutoff times, business-day calendars and holidays rather than by midnight UTC. A movement recorded at 23:50 on one side of a cutoff belongs to the next business date, so computing business_date as created_at::date makes daily totals disagree with every statement and every settlement file. The signature is a reconciliation break that resolves itself the following day and then reopens, which reads like a flaky job and is actually a data model that is missing a column: business_date has to be stored explicitly and set from the cutoff rule, with the timestamptz kept separately for ordering.

02

Retrying a charge after a timeout

A timeout is not a failure; it is an unknown outcome, and the request may have been processed in full with only the response lost. Re-sending it without an idempotency key that the processor itself honours produces a duplicate charge, which is a customer-visible incident and usually a dispute. The correct handling is to treat the state as unknown, query the processor for that key or client reference, and only then decide. The mechanism also depends on the key being generated once by the caller and reused across every attempt — generating a fresh key per retry turns the whole scheme into a no-op while leaving all the code that appears to implement it in place.

03

Quoting amortised or average cost as if it were a worst-case guarantee

Appending to a dynamic array is amortised O(1), but the append that triggers a resize copies every element, and hash lookup is constant only while the hash spreads the actual keys. Say which guarantee you are offering when the caller cares about the latency of one call rather than the total over many.

04

Reading the constraints as preamble rather than as part of the problem

The bounds are usually there to eliminate the obvious approach: n up to 10^5 makes an O(n^2) scan roughly 10^10 operations, far outside any per-test time budget, and an input larger than memory rules out loading it at all. When a bound is not given, ask for it, then say out loud which approach it kills.

Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.

11 technical prompts3 include a worked solution

Resolve party identity across merges without rewriting posted history

medium
union-findidentity resolutionimmutable history

Duplicate parties get merged over time: a stream of up to 5 million (loser_party_id, winner_party_id, merged_at) events, deliverable in any order, sometimes repeating a pair already merged. Ten million account rows and hundreds of millions of ledger_entry rows already reference pre-merge party ids and must not be rewritten. Build the structure that answers 'what is the canonical party for this id?' in near-constant time, and explain how a report that groups by party stays correct without touching a single historical row.

Approach
  1. The merge events form an undirected graph over party ids and the question is connected components, so use disjoint-set union with union by size and path compression: find and union amortise to O(alpha(n)), and alpha(n) is at most 4 for any n that exists. Five million merges over ten million ids is two integer arrays and a few hundred milliseconds.
  2. The domain twist: the representative cannot be whichever root union-by-size happens to pick, because the canonical party is a business decision recorded as winner_party_id. Keep union by size for the tree shape and a separate root -> canonical_party_id map for the label. Conflating them makes the canonical id flip when an unrelated merge reshapes the tree, and every report built on it moves retroactively.
  3. Never rewrite ledger_entry or account. Resolve at read through the canonical map: rewriting posted rows destroys the audit trail, locks hundreds of millions of rows, and buys nothing the map does not already give you.
  4. Materialise the map fully expanded as party_id -> canonical_party_id, one row per member rather than per edge, so a report is a single join instead of a recursive traversal. Rebuild it from the event log, which keeps it a derived artefact that can be regenerated rather than a second source of truth that drifts.
  5. Pin down determinism: a repeated merge is a no-op because both ids already share a root, but two events merging the same pair in opposite directions is a genuine conflict. Write the tiebreak down — lowest merged_at, then lowest id — or two replays of the same log produce different labels for the same data.
  6. Un-merges: disjoint-set union has no delete. If a merge can be reversed, keep the event log authoritative and rebuild the whole structure from the surviving events; at 5 million events that is seconds, and far cheaper than any incremental un-union scheme.
Follow-up
  • A merge is reversed after six months of postings. What exactly gets rebuilt, and what does the statement produced last month now say?
  • Two processes apply merge events concurrently. What guarantees they converge on an identical canonical map?
  • Resolving at read on the hot path versus materialising the map — where does that join actually live, and what does each cost?

Derive per-account balances and catch unbalanced transactions

easy
aggregationdouble-entrystreaming

You are given ledger_entry rows streamed in entry_id order: transaction_id, account_id, direction (debit or credit), amount_minor (a positive int64), currency, business_date. Up to 500 million rows, at most 20 million distinct (account_id, currency) pairs, and the entries of one transaction are contiguous in the stream. In a single pass with no re-reads, return the closing balance per (account_id, currency) and the transaction_id of every transaction whose entries do not sum to zero within each currency. State your time and space bounds.

Approach
  1. Normalise the sign at read time from direction, not from the amount: signed = +amount_minor for debit, -amount_minor for credit (state which convention you picked). The schema constrains amount_minor > 0 precisely so the sign lives in exactly one place.
  2. Hold one hash map keyed (account_id, currency) to an int64 running total. Twenty million keys at 16 bytes of payload plus map overhead is order 1 GB in most runtimes — quote the number, and offer the fallback: partition the stream by hash(account_id) % P and run P passes for 1/P of the memory.
  3. Ride the zero-sum check on the same pass. Because a transaction's entries are contiguous, keep a tiny currency -> int64 map for the current transaction_id only, test it against zero on the boundary and at EOF, then clear it. That is O(currencies in one transaction), typically one or two.
  4. Bound the arithmetic explicitly. Int64 holds about 9.22e18, so overflowing one account across 500 million entries needs an average of 1.8e10 minor units per entry — safe here, but use a checked add so an adversarial file fails loudly rather than wrapping.
  5. Complexity: O(n) time, O(distinct account-currency pairs) space, one sequential pass, no sort. The zero-sum check adds no asymptotic cost, which is the argument for doing it here rather than in a second job.
Follow-up
  • Entries of a transaction are no longer contiguous. What does the zero-sum check cost now, and which is cheaper: buffering open transactions or an external sort on transaction_id?
  • How would you produce the same balances as of an arbitrary business_date without a second full scan?
  • The job is restarted after a crash halfway through the file. What makes the second run produce identical output?

Detect duplicate-charge bursts in an out-of-order authorisation stream

hardWorked solution
sliding windowevent timewatermarksdeduplication

Authorisations arrive as (instrument_token_id, amount_minor, currency, event_time, arrival_time) at roughly 3,000 per second, up to 60 seconds late and out of order. Flag any token with three or more authorisations of identical (amount_minor, currency) inside any 10-minute window of event time. Report each flag once, as early as correctness allows. State memory per key and in total, how late an event you will accept, and what you do with one that arrives after you have already reported — or already declined to report — that window.

Approach
  1. Key state by (instrument_token_id, amount_minor, currency), not by token: the predicate is about identical amounts, so the window belongs to the triple. Each key holds its own event-time-ordered deque.
  2. In-order, this is two pointers: on insert, pop from the front while front <= new - 10 min, then flag if the deque reaches length 3. Amortised O(1) per event, memory proportional to that key's window occupancy.
  3. Out-of-order arrival breaks append-only monotonicity, so insert in position instead. With lateness bounded at 60 seconds the insertion point is always near the tail, so a short sorted vector or a 600-bucket per-second ring keeps it O(w) with tiny w; a balanced tree per key is correct but over-built for a one-minute reorder.
  4. Drive decisions off a watermark of max(event_time seen) - 60 s, never off wall clock. Anything older than the watermark is too late to change an answer and is counted in a late_dropped metric. Without an explicit watermark you have still chosen a lateness policy — you just cannot state it or test it.
  5. Report-once needs its own state: per key, a set of already-flagged 10-minute window ids. A later event inside an already-flagged window must not re-flag, and a late event that completes a window you never flagged must flag — which is why state is retired at window close plus the lateness bound, not at window close.
  6. Size it: state lives 660 seconds, so at 3,000 events per second there are about 1.98 million in flight, plus one flagged-window set per active key. Bound total memory explicitly and shed by key age, and say what shedding costs — a shed key can miss a flag, making the threshold a product decision rather than a tuning knob.
Worked solution 40 min
  1. Write the key, the window, the watermark and the report-once rule in four lines before any code; every later bug is one of these left implicit.
  2. Implement per-key state as a sorted deque of event times plus a set of flagged window ids, both retired at watermark - 660 s.
  3. Fixture 1, in order: identical amounts at t, t+1 min and t+9 min produce one flag. A fourth at t+11 min evicts t+1 exactly under the half-open rule pop while front <= new - 10 min, leaving two events and no second flag; change that comparison to strict < and the same input flags twice. Pick one and write it in the spec.
  4. Fixture 2, out of order: deliver the same three events as t+9, t, t+1 and assert the flag fires on the third arrival with the same window id as fixture 1.
  5. Fixture 3, too late: the t event arrives 90 seconds after the watermark passed it, so no flag fires and late_dropped increments by one.
  6. Replay all three fixtures under twenty random arrival orders and assert the flag set is identical whenever every event is inside the lateness bound.
EXPECTED RESULTFixture 1 flags exactly once, at the t+9 event; the t+11 event does not flag under the half-open rule and does under the closed one, which is the boundary the fixture exists to pin. Fixture 2 yields the same single flag with the same window id. Fixture 3 yields no flag and `late_dropped == 1`. All twenty shuffles produce an identical flag set.
Follow-up
  • Make the threshold and window configurable without rebuilding all in-flight state on every change. What does that constrain in the data structure?
  • Two stream partitions hold events for the same token. What does that force the partitioning key to be?
  • An event arrives three hours late. Does any answer change, and who is told?

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.

Small steps. Visible outcomes.0 / 7 completed
ONE WEEK · YOUR PACE

Prepare, practise & reflect

One practical outcome each day. Spend longer where you need it.

0 / 7 done
01Coding, one pass at shallow depth
  • Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
  • For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
  • Fix nothing today. The value of the pass is the unfixed record.

Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.

Practice prompt ↗Practice prompt ↗Worked solution ↗
02Design, one pass at shallow depth
  • Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
  • After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
  • Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.

Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.

Practice prompt ↗Practice prompt ↗
03Fundamentals and the practical rounds
  • Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
  • Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
  • Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.

Deliverable: Eight scored short answers and one written reading of unfamiliar code.

Practice prompt ↗Practice prompt ↗
04The rounds that are about you, and the map
  • Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
  • Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
  • Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.

Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.

Practice prompt ↗Practice prompt ↗Worked solution ↗
05First chosen area, to the depth you skipped
  • Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
  • After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
  • Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.

Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.

Practice prompt ↗Practice prompt ↗
06Second chosen area, where the gap is coverage rather than speed
  • Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
  • Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
  • Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.

Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.

Practice prompt ↗Practice prompt ↗
07Reassemble the loop
  • Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
  • Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
  • Reduce the week to one page holding only the rules you can state without reading them.

Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.

Practice prompt ↗Practice prompt ↗Worked solution ↗

Expand any day for tasks and deliverables. Your progress is saved on this device.

Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.

Ship payouts with named, scheduled reconciliation debt

medium
technical debtreconciliationscope cutting

You have six working days to ship merchant payouts. The correct reconciliation join is on (external_reference, amount_minor, currency, business_date); the version that fits the window matches on amount and business_date alone, which will mismatch same-amount, same-day lines. Describe a time you shipped with debt you knew about. State exactly what you cut, the guard that made the shortcut visible rather than silent, who agreed to carry the risk, the date it was paid down, and what it cost while it stood. Be specific about the failure you were accepting.

Approach
  1. The probe is whether your shortcuts are bounded and instrumented or merely undocumented. Start by stating the failure you accepted in one concrete sentence: ambiguous matches will be resolved arbitrarily, so a genuine break can be masked by pairing with the wrong line.
  2. Make the shortcut loud rather than silent. When more than one candidate matches, do not pick one: open a break of type duplicate_match and emit a counter on that path. A shortcut that reports its own frequency converts an unknown into a number you can bring to the paydown conversation.
  3. Bound the exposure before you ship: query historical settlement files for lines that collide on (amount_minor, business_date) within a merchant. If that is 0.2 percent of lines, the risk is small and quantified; if it is 8 percent, the plan does not survive contact and you have learned that before shipping rather than after.
  4. Name the owner and the date, and say where they are written down. 'The team agreed' is the generic answer; a ticket with an owner, a date, and the counter that forces the conversation is the strong one.
  5. Distinguish debt you can pay from debt that compounds. A matching shortcut that leaves breaks visible is recoverable; one that auto-resolves breaks by widening a tolerance destroys the evidence and cannot be unwound later, and a strong answer says it would have refused that version even under the deadline.
  6. Report the actual cost: how many ambiguous matches occurred, how much analyst time they consumed, and whether the paydown date held. If it slipped, say by how long and what finally forced it.
Follow-up
  • The paydown date arrives and the team has a new deadline. What do you do differently from the first conversation?
  • Which shortcut would you have refused to ship at any deadline, and why that one?
  • How would you have sized the exposure if no historical settlement files were available?

Estimate a reconciliation rebuild you have never attempted

hard
estimationunknownsreconciliation

You are asked how long it takes to replace a reconciliation service matching 30 million settlement lines a day against the ledger, including a bounded fuzzy fallback for netted fees and an ageing model for breaks. You have never built one. Produce an estimate, the range around it, and the two or three unknowns that dominate that range. Then describe a time you estimated unfamiliar work: what you did in the first day to shrink the range, what you committed to publicly, how far off you were, and what you would tell the requester differently now.

Approach
  1. The probe is whether you can be useful under uncertainty without either refusing to estimate or inventing false precision. Give a number with an explicit range and the basis for both, then immediately name what would move it, rather than asking for two weeks of discovery first.
  2. Decompose into parts with different uncertainty profiles. The hash join on (external_reference, amount_minor, currency, business_date) over 30 million lines is well understood engineering and estimates tightly; the fuzzy fallback for netted and fee-adjusted lines does not, because its scope is defined by whatever the files actually contain; the ageing and break workflow is mostly operations-facing surface area, which estimates by counting screens and states.
  3. Name the dominating unknowns concretely: how many distinct file formats and cutoff conventions the sources use, what fraction of lines are netted rather than itemised, and whether business_date is derivable from any field in the file or must be reconstructed from the cutoff rule. Each is a factor on the fuzzy path, not a percentage on the whole.
  4. Describe the first-day range-shrinking work, which is the part that separates strong from generic: take one real file, count distinct formats, measure the netted fraction, and attempt the exact join on a single day of postings to see what the residual actually is. One day of that typically converts a 3x range into something near 1.5x.
  5. Commit in a form that survives being wrong: a range plus a checkpoint date at which you will replace it with a narrower one, and an explicit statement of what you will cut first if the range turns out to be optimistic.
  6. In the retrospective half, give the real numbers: the estimate, the actual, and the specific thing that consumed the difference. Answers that were within 10 percent are less informative than answers that were 2x off for a nameable reason.
Follow-up
  • The requester wants one number, not a range, for a board deadline. What do you give them?
  • Your one-day probe finds 40 percent netted lines instead of the 5 percent you assumed. What changes in the plan, not just the estimate?
  • What do you cut first if you are at the deadline and the fuzzy fallback is not done?

Argue against a dual write you were assigned to build

hard
dual writeoutboxdesign reviewdisagreement

A senior engineer specifies that the orchestration service should update payment_intent.status and publish the merchant event in the same code path, wrapping the publish in a retry. You believe it is wrong and you have been told to build it. Describe a time you argued against a design you were assigned. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that did not persuade anyone, and why it did not.

Approach
  1. The probe is whether you can disagree with a technical authority using evidence rather than taste, and still execute. Establish the failure precisely first: no transaction spans the database and the broker, so a crash between them leaves one side done, and it fails asymmetrically in both orderings. Publish-then-commit invents events for state that never existed; commit-then-publish loses events for state that does.
  2. Kill the retry argument explicitly, because it is the one that keeps the design alive: the retry loop lives in the same process that can die, so it narrows the window and never closes it. Quantify the window if you can (deploy restarts per week times request rate times the in-flight fraction) rather than asserting it is rare.
  3. Bring evidence in the form the decision-maker can check in a day: a count of merchant events with no corresponding intent version, or intent versions with no event, over a window you can query now. An argument that costs the other person nothing to verify is the one that moves.
  4. Say what you proposed instead in one sentence with its cost owned honestly: an outbox row written in the same transaction with UNIQUE (aggregate_type, aggregate_id, aggregate_version), a partial index on published_at IS NULL, and a relay that delivers at least once. The cost is that every consumer now has to be idempotent, and that is a real tax you are asking others to pay.
  5. Describe the disagree-and-commit mechanics concretely: what you built, what you instrumented so the prediction could be checked, and what threshold would have proved you wrong. A strong answer is falsifiable; a generic one says 'I raised concerns and moved on'.
  6. Report the outcome including the possibility that you were partly wrong about severity or timing, and separate 'I was right' from 'the disagreement was handled well'.
Follow-up
  • You lost the argument. What instrumentation do you add so the question gets settled by data in a month rather than by another meeting?
  • The stream processor's documentation says exactly-once. Why does that not settle the question for a card charge?
  • What would have made you drop the objection entirely?
  • 01

    You have six working days to ship merchant payouts. The correct reconciliation join is on (external_reference, amount_minor, currency, business_date); the version that fits the window matches on amount and business_date alone, which will mismatch same-amount, same-day lines. Describe a time you shipped with debt you knew about. State exactly what you cut, the guard that made the shortcut visible rather than silent, who agreed to carry the risk, the date it was paid down, and what it cost while it stood. Be specific about the failure you were accepting.

  • 02

    You are asked how long it takes to replace a reconciliation service matching 30 million settlement lines a day against the ledger, including a bounded fuzzy fallback for netted fees and an ageing model for breaks. You have never built one. Produce an estimate, the range around it, and the two or three unknowns that dominate that range. Then describe a time you estimated unfamiliar work: what you did in the first day to shrink the range, what you committed to publicly, how far off you were, and what you would tell the requester differently now.

  • 03

    A senior engineer specifies that the orchestration service should update payment_intent.status and publish the merchant event in the same code path, wrapping the publish in a retry. You believe it is wrong and you have been told to build it. Describe a time you argued against a design you were assigned. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that did not persuade anyone, and why it did not.

PracHub interview preparation framework ↗
Is this an official CPP Investments | Investissements RPC interview guide?

No. It is PracHub's own research and practice material for the Software Engineer role at CPP Investments | Investissements RPC. Rounds and questions reflect what candidates have reported, not a process CPP Investments | Investissements RPC has published, and they change over time. Confirm the current format and scope with your recruiter.

PracHub interview research ↗
How much time should I spend preparing for the technical interviews?

Dedicate at least 2–4 weeks to reviewing core data structures, algorithms, and system design principles. Focus on being able to explain your thought process clearly, as this is often weighted as heavily as the solution itself.

PracHub interview research ↗
What is the culture like for engineers at the firm?

The culture is professional, collaborative, and focused on long-term value. You will be surrounded by high-performing peers who value intellectual rigor and precision.

PracHub interview research ↗
Will I be working directly with investment teams?

Yes, collaboration with investment professionals is a core part of the role. You will often act as a bridge between technical implementation and business strategy.

PracHub interview research ↗
Sources & methodology 3 sources ↗

No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.