As a Software Engineer at Maverick Financial Group, you work to bridge the gap between complex financial requirements and robust, scalable technical solutions. This role is critical to maintaining the high-performance systems that Maverick Financial Group's clients rely on, requiring a blend of technical precision and the ability to translate business needs into clean, maintainable code. You will contribute to a variety of projects, ranging from backend system architecture and automation to data-driven application development.
The work environment at Maverick Financial Group is defined by its collaborative spirit and focus on practical problem-solving. Candidates report that interviewers look for engineers who can navigate ambiguity, work effectively within multidisciplinary teams, and approach technical challenges with a pragmatic mindset. Whether you are troubleshooting legacy systems or developing new features, your impact is measured by your ability to deliver reliable results that move the business forward.
Recruiter Screen
reportedBefore anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Technical Assessments
reportedWhat 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
Meet Engineering Managers
reportedYou 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 editorial advice for the preparation topics above.
Treating money as a decimal with two places
ISO 4217 exponents are 0 for currencies such as JPY and KRW, 2 for most, and 3 for BHD, KWD, JOD, OMR and TND, so a hard-coded multiply-by-100 is off by a factor of 100 or 10 depending on the currency, in opposite directions. Floating point is worse: IEEE 754 binary64 cannot represent 0.1 exactly, so repeated accrual accumulates drift that appears as a handful of minor units in the daily reconciliation and then gets 'fixed' by widening the match tolerance, which is how a genuine break becomes invisible. The only forms that survive a reconciliation are integer minor units with the exponent carried alongside the currency code, or a fixed-scale decimal type with exactly one documented rounding point.
Calling a compensating action a rollback
A saga's compensation is a new, externally visible business event, not an undo. A refund after a capture leaves both movements on the customer's statement, may not return the scheme fee, and lands days later rather than immediately. Designing a multi-service flow as though the compensation restores the prior state produces flows that turn out to be unimplementable at the final step, when the thing that needs undoing has already left the building. The sequence has to be ordered so the irreversible step is last and the reversible ones precede it, with an explicit pending state shown to the customer while a compensation is in flight.
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.
Sharing mutable state with no stated owner
Say which thread, request or task owns each mutable structure, and what protects it when the answer is more than one: a lock, a queue that hands ownership across, or an immutable copy per reader. A structure documented as safe for concurrent reads is usually not safe for a concurrent write alongside those reads.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Detect duplicate-charge bursts in an out-of-order authorisation stream
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
- 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. - 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. - 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.
- 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 alate_droppedmetric. Without an explicit watermark you have still chosen a lateness policy — you just cannot state it or test it. - 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.
- 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.
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?
Resolve party identity across merges without rewriting posted 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
- 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:
findandunionamortise 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. - 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 separateroot -> canonical_party_idmap 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. - Never rewrite
ledger_entryoraccount. 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. - 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. - 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. - 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?
Rank merchants by unmatched break value in one streamed pass
A reconciliation run streams up to 50 million break rows of (merchant_id, currency, delta_minor signed, break_type). Return the 50 merchants with the largest total absolute delta_minor in a single currency, from one pass, with memory that does not grow with merchant count — there are up to 8 million distinct merchants and room for roughly 200,000 counters. Give both an exact and an approximate design, state the error bound the approximate one actually guarantees, and say which you would put in the nightly job.
Approach
- Start by testing whether the constraint is real. Exact needs one hash map
merchant_id -> int64plus a size-50 min-heap: O(n) to aggregate, O(m log 50) to rank, O(m) space. At 8 million merchants and 16 bytes of payload that is a few hundred megabytes — quote the number before reaching for a sketch. - If it genuinely does not fit, exact still costs only two passes or an external group-by: partition by
hash(merchant_id) % Pto disk, aggregate each partition independently, then merge with a K-way heap. This is the answer whenever exactness is non-negotiable, which for money it usually is. - One-pass approximate: weighted Space-Saving with C counters. An item either hits an existing counter or evicts the minimum one and inherits its value as an over-estimate. With total weight W and C counters, every reported total over-estimates by at most W/C, and any merchant whose true total exceeds W/C is guaranteed to be in the table. Quote W/200,000 as a fraction of the day's break value.
- State what the bound does not give you: the ordering within the top 50 is not guaranteed, and a merchant just below the threshold can be missed entirely. The honest claim to an operations reader is 'contains everyone above 0.0005% of today's break value', not 'the top 50'.
- Decide the metric before either design, because
sum(abs(delta))andabs(sum(delta))are different questions: a merchant with offsetting +1,000,000 and -1,000,000 breaks is top-ranked under the first and invisible under the second. Break work wants the first. - Recommend exact (two-pass or external group-by) for the nightly job — it runs once, has hours of budget, and an analyst acts on its output. Keep the sketch for a live dashboard, where a cheap bounded approximation beats an exact number that is four hours stale.
Worked solution 25 min
- Fix the metric in writing as
sum(abs(delta_minor))per(merchant_id, currency), because that is what an analyst works from. - Implement exact: hash-map aggregate, then a size-50 min-heap — push while the heap is under 50, then push-pop only when an item beats the root.
- Implement weighted Space-Saving with 200,000 counters over the same stream.
- Generate 50 million rows with a Zipfian merchant distribution near s = 1.1 so a few merchants dominate, then generate a uniform variant for contrast.
- Compare: how many of the exact top 50 the sketch recovers, and the largest observed over-estimate, against the W/C bound.
Follow-up
- Totals are now needed per currency as well as overall. What does that do to the key and to the counter budget?
- Prove the Space-Saving over-estimate bound to me in two sentences.
- The nightly job restarts halfway. Is the aggregate idempotent, and what does the heap do with a partially consumed stream?
Rebuild a statement with a running balance from entries alone
From ledger_entry(entry_id bigint and monotonic, account_id, direction, amount_minor, currency, business_date, posted_at) produce one month's statement for a single account: every entry in order, its signed amount, and the running balance after it, starting from a supplied opening balance. The account is a customer deposit, so a credit increases it. Also return the first business_date in the month on which the running balance went negative, or null if it never did. Write the query using window functions, state which frame you depend on, and say what makes the ordering deterministic.
Approach
- Sign the amount first: CASE WHEN direction = 'credit' THEN amount_minor ELSE -amount_minor END, because the sign lives in direction and never in the column. State the convention out loud, since a customer deposit is a liability of the institution and a credit increases it, while for an asset account the same expression inverts.
- Compute the running balance as SUM(signed) OVER (PARTITION BY account_id ORDER BY business_date, entry_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), then add the opening balance as a scalar. The opening figure is a constant for the statement, not a second window.
- Be explicit about the frame, because it is the whole exercise: with an ORDER BY and no frame clause the default is RANGE UNBOUNDED PRECEDING AND CURRENT ROW, which includes every peer row tied on the ordering key. Ordering on business_date alone therefore reports the same end-of-day figure on every line of that day, and the statement still looks plausible.
- Make the order deterministic with a column that has no ties: business_date has ties by construction, entry_id is monotonic and unique, and posted_at is neither guaranteed unique nor correct as an ordering key when a backdated entry posts late.
- Get the first breach from the same computation rather than a second pass: wrap the running balance in a subquery and take MIN(business_date) FILTER (WHERE running_balance_minor < 0). LAG is the wrong tool here, because the question is about a level rather than a change.
- Support it with an index on (account_id, business_date, entry_id) so the partition and the order both come from the index; then confirm in EXPLAIN that there is no Sort node above the index scan.
Follow-up
- An entry for 3 March posts on 7 March, after the statement for that week was sent. Where does it appear, and what does the running balance do?
- The same statement has to be reproducible a year from now. What stops it changing, and what would silently change it?
- At what account size does deriving this per request stop being viable, and what would you materialise first, a daily closing balance or a monthly one?
Add and backfill business_date on a live ledger table
ledger_entry holds 4 billion rows, is append-only, takes 10,000 inserts per second, and every reporting query currently derives the business date as posted_at::date. You must add business_date date NOT NULL, populated from the cutoff rule (17:00 in the account's own timezone), backfilled across all history, indexed, and cut over, with no write downtime and no long-held lock. Give the ordered migration steps with the lock each one takes, how you make the backfill restartable and throttled, and how you retire the old expression safely.
Approach
- Add the column nullable and with no default. ALTER TABLE ... ADD COLUMN takes ACCESS EXCLUSIVE but is a catalogue-only change held for microseconds. The hazard is the lock queue, not the statement: a blocked ALTER waits behind one long reader holding ACCESS SHARE, and every query arriving afterwards queues behind the ALTER's pending ACCESS EXCLUSIVE, so set lock_timeout to a couple of seconds and retry rather than letting a metadata change take the table down.
- Deploy the write path before the backfill, so new inserts populate business_date from the cutoff rule while reads stay on the old expression. The backfill then chases a closed set with a fixed upper bound instead of a moving target.
- Backfill in bounded batches keyed by entry_id range, on the order of 50,000 rows per statement, committing between batches and recording the high-water mark in its own table so a killed run resumes instead of restarting. WHERE business_date IS NULL makes each batch idempotent, and the pacing is set by replica lag and dead-tuple growth rather than CPU, since each UPDATE writes a new row version and the WAL volume is proportional to the rows touched.
- Install the constraint without a blocking scan: ALTER TABLE ... ADD CONSTRAINT ck_business_date CHECK (business_date IS NOT NULL) NOT VALID takes a brief ACCESS EXCLUSIVE and scans nothing, then VALIDATE CONSTRAINT takes SHARE UPDATE EXCLUSIVE and runs alongside reads and writes. On PostgreSQL 12 and later, SET NOT NULL can then use the validated CHECK and skip its own full scan; on 11 and earlier it always scans, so the CHECK is the migration on those versions.
- Build the index with CREATE INDEX CONCURRENTLY, which avoids ACCESS EXCLUSIVE at the cost of two table passes, cannot run inside a transaction block, and on failure leaves an INVALID index that must be dropped and rebuilt rather than reused.
- Cut over behind a flag: run the new and old expressions side by side for one reporting cycle and compare totals per day, since the cutoff rule will legitimately move entries near 17:00 across the boundary. Only once they reconcile do you retire the posted_at::date expression index, and you keep posted_at as the ordering key rather than repurposing it.
Worked solution 40 min
- Write the migration as numbered SQL statements, annotating each with its lock mode and expected duration, and set lock_timeout plus a retry around every ALTER.
- Rehearse on a 10-million-row copy under a concurrent insert load, measuring per-batch duration, WAL generated and replica lag.
- Kill the backfill at a random point, restart it, and confirm it resumes from the high-water mark.
- Add the CHECK as NOT VALID, VALIDATE it while inserts continue, then SET NOT NULL and build the index CONCURRENTLY.
- Run old and new date expressions side by side for one cycle and diff the daily totals before dropping the old expression index.
Follow-up
- Reporting now wants ledger_entry partitioned by business_date. Why can this not be another ALTER, and what is the migration instead?
- Nightly totals move for the days around the cutoff change. How do you tell a correct restatement from a backfill bug?
- The backfill is halfway done when a replica falls 20 minutes behind. What do you throttle, and what do you refuse to throttle?
Can you explain the basics of JavaScript or PHP as applied to our curr…
Can you explain the basics of JavaScript or PHP as applied to our current stack?
Approach
- State your assumptions explicitly before working the problem.
- 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
- How would you know your answer was wrong?
- What assumption would you test first?
Walk me through a scenario where you encountered a significant roadblo…
Walk me through a scenario where you encountered a significant roadblock and how you resolved it.
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Paginate an append-only statement endpoint under continuous writes
Statement reads come from ledger_entry (entry_id bigint monotonic primary key, transaction_id uuid, account_id bigint, direction, amount_minor bigint, currency, source_type, source_id, business_date date, posted_at timestamptz). The caller is a merchant dashboard fetching one account newest-first, 100 rows per page, while new entries are still being appended. Offset pagination is in production and support reports rows appearing on two consecutive pages. Design the replacement: the index, the exact WHERE clause and ORDER BY, the cursor's contents and encoding, and what happens when a caller reuses a cursor an hour later.
Approach
- Name the mechanism of the duplication rather than blaming the sort. Each page is a separate statement with its own snapshot; under a descending order, every row inserted between page 1 and page 2 shifts the whole result one position later, so the last row of page 1 reappears as the first row of page 2. The cost is bad too: OFFSET n scans and discards n rows, so deep pages are O(offset + k).
- Replace it with a keyset predicate over the unique, monotonic ordering column: WHERE account_id = $1 AND entry_id < $cursor ORDER BY entry_id DESC LIMIT 100. That is one index descent plus 100 rows, O(log n + k), and the cost does not change with depth.
- Index (account_id, entry_id). A B-tree is scanned in either direction at the same cost, so a separate DESC index buys nothing. If the endpoint also filters on a date range, the tuple becomes (account_id, business_date, entry_id) and the predicate becomes a row-value comparison, (business_date, entry_id) < ($d, $e), which still resolves to a single descent.
- Make the cursor opaque and self-describing: base64 of the anchor tuple plus a hash of the filter set and an encoding version. A cursor presented with a different account or date range is rejected with a distinct code rather than quietly returning another account's rows.
- State the validity rule. Because the table is append-only, the anchor row is never deleted, so a keyset cursor stays valid indefinitely and an hour-old cursor simply resumes. Add one caveat for machine consumers: entry_id is allocated before commit, so an entry with a lower id can become visible after a reader has already passed it. A human statement page never notices, but a feed consumer needs a lag window or a commit-ordered marker rather than the newest id.
Worked solution 25 min
- Insert 500 entries for one account, fetch LIMIT 100 OFFSET 0 ordered by entry_id DESC, insert five more entries, then fetch LIMIT 100 OFFSET 100 and record which entry_ids appear in both results.
- Write the keyset query, repeat the identical interleaving, and confirm no entry that existed at page-1 time is returned twice or skipped.
- Run EXPLAIN (ANALYZE, BUFFERS) on both queries at offset 0 and at offset 100000 and compare the buffers read.
- Define the cursor as base64 of {entry_id, filter_hash, v} and implement the rejection branch for a filter_hash mismatch.
- Write down the allocation-versus-commit caveat and decide explicitly whether this endpoint is a human statement view (no mitigation needed) or a machine feed (needs a lag window).
Follow-up
- The dashboard now wants to sort by amount_minor descending. What breaks, and what does the cursor have to contain to fix it?
- A caller asks for a total page count. What does that cost on a 400-million-row table, and what do you offer instead?
- How would you support paging backwards from a cursor without re-sorting in the application?
Reconciliation breaks that close overnight and then reopen
Reconciliation opens 400 to 900 ledger_only breaks each morning, most resolve on the next run, and a subset reopens two days later. The three-way match joins on (external_reference, amount_minor, currency, business_date). ledger_entry.business_date is populated as posted_at::date, where posted_at is timestamptz. The processor closes its day at 22:00 US/Eastern. Breaks cluster on movements posted between 00:00 and 03:00 UTC. Give the ordered checklist, the cause, the fix, and what happens to the breaks already in the table.
Approach
- Test the hypothesis with data you already have rather than by reasoning about it. Bucket open breaks by hour of day on posted_at. A cutoff mismatch concentrates breaks in a fixed window every day and produces almost none outside it; a flaky job produces breaks with no time-of-day structure. One query eliminates half the candidate explanations.
- Compute the offset explicitly and note that it moves. A 22:00 US/Eastern cutoff is 03:00 UTC next day under EST and 02:00 UTC under EDT, so the processor's business date D spans UTC 03:00 on D to 03:00 on D+1, while posted_at::date assigns D to UTC 00:00 through 24:00. The disagreement window is exactly 00:00 to 03:00 UTC, narrowing to two hours during EDT, which matches the observed cluster.
- Find the second bug in the same expression. Casting a timestamptz to date in PostgreSQL applies the session TimeZone, so the identical query returns different dates for different sessions and for the same session after a SET. A business date that depends on who is asking is not a business date.
- Name the missing column rather than a better expression. business_date is a business fact determined by a cutoff rule and a business-day calendar; it is not derivable from any UTC timestamp. It must be stored and set at write time from the rule, with posted_at kept separately for ordering.
- Account for the self-resolving and reopening pattern before claiming the cause is complete. Check whether the day-spanning fuzzy fallback is resolving breaks by matching against an adjacent date, and whether two movements sharing amount, currency and merchant on adjacent dates can be crossed by it. A crossed pairing resolves today and surfaces as duplicate_match later, which is exactly the resolve-then-reopen shape and is a second reason not to widen tolerance.
- Plan the correction against the append-only rule. The trigger forbids UPDATE on a posted entry, so a business_date correction is either a deliberate, logged exception for a non-financial classification column with the trigger amended, or a separate correction table joined at read time. Pick one and get it signed off, because it decides whether prior statements change. Then re-run the matcher over corrected dates rather than bulk-resolving the open breaks; whatever survives is the genuine break population the date bug was masking.
Follow-up
- The fuzzy fallback resolved some breaks by matching an adjacent date. How do you prove it never crossed two lines?
- What does your fix do on the DST transition days themselves, when the local day is 23 or 25 hours long?
- Where does the business-day calendar live, who updates it for a newly announced holiday, and what breaks if they forget?
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Rebuild the primitives by implementing them
- Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
- Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
- For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.
Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Arrays under an invariant: two pointers, sliding window, binary search
- Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
- Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
- Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.
Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.
Practice prompt ↗Practice prompt ↗03Sorting, heaps, and the greedy argument that has to be proved
- Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
- Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
- Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.
Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.
Practice prompt ↗Practice prompt ↗04Recursion, memoisation, and the step to a table
- Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
- Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
- Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.
Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Graphs, where most of the work is choosing the traversal
- Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
- Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
- Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.
Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.
Practice prompt ↗Practice prompt ↗06One day for everything that is not an algorithm
- Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
- Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
- Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.
Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.
Practice prompt ↗Practice prompt ↗07Solve out loud, under time
- Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
- Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
- Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.
Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Nobody is scoring your stamina at three in the morning. What carries weight is which signal told you something was wrong, what you measured before touching anything, what you rolled back versus what you fixed forward, and why you picked one. 'We restarted it and it went away' is a story about not knowing.
Describe a time when you had to meet a particularly fast deadline.
Describe a time when you had to meet a particularly fast deadline.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
What are your greatest professional strengths and your areas for impro…
What are your greatest professional strengths and your areas for improvement?
Approach
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Tell me about a time you had to work on a project without direct super…
Tell me about a time you had to work on a project without direct supervision.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
What is your experience with Object-Oriented Programming (OOP) princip…
What is your experience with Object-Oriented Programming (OOP) principles?
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
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
- 01
Describe a time when you had to meet a particularly fast deadline.
- 02
What are your greatest professional strengths and your areas for improvement?
- 03
Tell me about a time you had to work on a project without direct supervision.
- 04
What is your experience with Object-Oriented Programming (OOP) principles?
Is this an official Maverick Financial Group interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Maverick Financial Group. Rounds and questions reflect what candidates have reported, not a process Maverick Financial Group has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the interview process typically take?
The timeline varies, but candidates often receive a decision within 1–2 weeks after the final round. Candidates report a steady pace throughout the process.
PracHub interview research ↗What is the best way to prepare for the technical rounds?
Focus on your past projects and be ready to explain the "why" behind your technical decisions. Refresh your knowledge of fundamental data structures and common language concepts.
PracHub interview research ↗Is the culture collaborative or competitive?
Candidates describe a highly collaborative atmosphere at Maverick Financial Group. Teams are set up to support one another, and interviewers look for candidates who value shared success over individual accolades.
PracHub interview research ↗Can I expect remote or hybrid work options?
The work environment depends on the specific team and location. Raise any location-based preferences or requirements during your initial recruiter screen.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22