A Software Engineer at Stellar Solutions plays a pivotal role in maintaining and advancing the company's technical infrastructure. Whether working on complex Mainframe systems or modernizing web experiences through ReactJS and NextJS, your contributions are essential to the stability and scalability of the company's service offerings. The role calls for problem solving that keeps Stellar Solutions' technical architecture in pace with evolving business needs.
The work is both challenging and rewarding, requiring a blend of legacy system maintenance and modern application development. You will collaborate with cross-functional teams to deliver high-quality software that directly affects the company's operational efficiency and client satisfaction. Success in this role requires a disciplined approach to development, a commitment to clean code, and the agility to navigate different technology stacks depending on the specific project requirements.
Initial Technical Screen
reportedMost of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Problem-Solving Dive
reportedBecause the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.
What to demonstrate
- Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
- Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
- Whether you can be redirected in the middle of an answer without restarting it from the beginning
- Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it
How to prepare
- Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
- Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
- Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing
Project Experience Review
reportedAn unlabelled round is first an information problem, and the cheapest information is free. Whoever schedules it can usually tell you how long it runs, who will be in the room and what they work on, whether you will be writing code and in what environment, and whether anything is being sent beforehand. Ask in writing so the answer is on record, then prepare for the two or three formats those answers still leave open instead of betting on one. What separates a strong candidate is not guessing right; it is having an opening that works whichever one it turns out to be.
What to demonstrate
- Whether you can start work from an ambiguous brief, since tolerating a vague scope without stalling is the same thing the job asks for
- Whether the questions you asked beforehand were ones that change your preparation, such as duration, medium and who is joining, rather than ones whose answers you could not have acted on
- Whether you adapt when the round turns out to be something other than what you were told, instead of spending the first ten minutes visibly recalibrating
How to prepare
- Send one short scheduling message asking four things: how long, who is joining and what they work on, whether you will be writing code and where, and whether to prepare anything in advance. Treat a vague reply as real information, since it means the round is loosely structured and you will be shaping it yourself.
- Write one opening that works in any of the formats still open: restate in your own words what you have been asked to do, then ask which of two directions is more useful to them. Say it aloud until it stops sounding recited.
- Set up for the two most likely formats before the call starts, with a blank editor in the language you would choose and a shared document you can type into, so a format surprise costs you nothing in the first minutes
PracHub editorial advice for the preparation topics above.
Depending on a client field because it is always populated
It is populated by their operational process, not by a constraint in their database, and their process changes without telling you. When it stops being populated, a permissive pipeline writes nulls or defaults into derived data and the damage spreads for weeks before anyone notices. Validate at the boundary and fail the run with failure_class='schema_mismatch' plus a redacted sample, so the failure is loud, attributable to the client's change, and cheap to diagnose. A run that fails clearly is far cheaper than a backfill that has to reconstruct which rows were silently wrong.
Forking the product per client instead of building a configuration or extension point
A fork is the cheapest possible answer for one client and the most expensive possible answer for the estate. Maintenance cost grows as O(clients x changes): a single security patch now needs one port per fork, each with its own conflicts, its own test run and its own release window, and the forks drift further apart with every port. The honest rule is that a required fork is a product gap, so it should either become a supported extension point with a stable interface, or be an explicit, time-boxed and separately priced artefact that is never expected to receive upstream fixes.
Trusting input because it came from your own front end
Anything crossing a trust boundary is hostile: parameterise queries instead of building SQL by concatenation, validate against an allow-list rather than a deny-list, and bound the size of anything you allocate from a request. Raising this unprompted in an API or design question is a cheap and unusually strong signal.
Finishing a solution without stating its complexity
Give time and space in the same breath as the code, and define n explicitly when there are two sizes, since n nodes and m edges are not interchangeable. Space is the half that gets skipped: count the auxiliary structures you allocate and the recursion stack at its deepest, not only the answer you hand back.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Top-k longest connector runs from a stream too large to sort
A day of connector_run rows sits in object storage: 200,000,000 rows, unsorted, far larger than memory, and streamable once. Each row has run_id, connector_id, started_at and finished_at, where finished_at is NULL for runs that never reached a terminal state. Return the 200 longest terminated runs by (finished_at - started_at), ties broken in favour of the lower run_id, plus the count of rows with a NULL finished_at. You may not sort the input or hold it in memory. State the time and space complexity you achieve.
Approach
- Keep a bounded min-heap of capacity k=200 ordered on (duration, -run_id). The root is then the weakest retained entry: smallest duration, and on a duration tie the largest run_id, which is exactly the one the stated tie-break should evict.
- Per row: if finished_at is NULL, increment a counter and skip; otherwise compute the duration and push when the heap holds fewer than k, else compare against the root and replace only if strictly better. O(n log k) worst case, O(k) space, and after the heap warms nearly every row costs a single comparison against the root.
- Reject the alternatives explicitly. A full sort is O(n log n) and at 2e8 rows means an external merge sort over hundreds of gigabytes to produce 200 rows. Quickselect is O(n) average but needs the whole array resident, which the constraint forbids.
- Handle the NULL as data, not as an edge case: depending on the language, subtracting from NULL either throws or yields a sentinel that lands at the top of the list. Count those rows and report them, because a spike of unterminated runs is its own signal.
- To parallelise, give each of p shard readers its own size-k heap and merge the p results. The union of per-shard top-k sets contains the global top-k, because an element beaten by fewer than k rows globally is beaten by fewer than k rows in its own shard, so the merge is exact at O(pk log k).
Follow-up
- Now return the top 20 bindings by total run time instead of the top runs. What changes, and how many distinct keys must you hold to make it exact?
- The key space is too large to hold an exact aggregate. What does a Misra-Gries summary with m counters actually guarantee about the counts it returns?
- Rows now arrive continuously and you want a rolling one-hour top-k. Does the bounded heap still work, and if not, what breaks?
Assign connector bindings to rollout waves and name the cycle
Connector bindings declare ordering dependencies: an edge u -> v means v must not run in a wave earlier than one after u has succeeded. You have up to 50,000 bindings and 200,000 edges, and a misconfigured dependency may have created a cycle. Assign each binding the earliest wave number such that every predecessor sits in a strictly earlier wave, return the total wave count, and if a cycle exists return the bindings on one concrete cycle rather than a boolean. The implementation must be iterative: a dependency chain can be 50,000 deep.
Approach
- Build an adjacency list and an indegree array, then run Kahn's algorithm from every indegree-zero node. Bindings with no predecessors get wave 0. O(V+E) time, O(V+E) space.
- Relax waves as you decrement: when popping u, set wave[v] = max(wave[v], wave[u]+1) for each successor before enqueueing v at indegree zero. Because v is only enqueued after its last predecessor is popped, wave[v] is 1 + max over predecessors, which is the earliest legal wave. Total wave count is max(wave)+1, the longest path in nodes.
- If Kahn emits fewer than V nodes, the residual subgraph is exactly the nodes lying on or downstream of a cycle. Every residual node still has an in-edge inside the residual, otherwise it would have been popped, so walking backwards along in-edges cannot dead-end and must revisit a node within |residual| steps; the segment between the two visits is a concrete cycle. O(V) with a visited-position map.
- Use an explicit stack or a queue rather than recursion. Recursive DFS on a 50,000-node chain exceeds the default stack in most runtimes, and the failure looks like a crash rather than a cycle.
- At this size, recompute the whole plan whenever an edge changes. Incremental topological order maintenance costs far more code than the O(V+E) rebuild it saves.
Worked solution 25 min
- Take seven bindings A,B,C,D,E,F,G with edges A->C, B->C, C->D, F->G, G->F. Indegrees: A0 B0 C2 D1 E0 F1 G1.
- Kahn from [A,B,E]: pop A (wave 0), relax C to wave 1 and indegree 1; pop B (wave 0), relax C to wave 1 and indegree 0, enqueue C; pop E (wave 0).
- Pop C (wave 1), relax D to wave 2 and indegree 0, enqueue D; pop D (wave 2). Five of seven nodes emitted.
- Residual is {F,G}. Walk backwards from F: F's in-edge comes from G, G's in-edge comes from F, so F repeats and the cycle is F -> G -> F.
- Verify the wave assignment against every edge before reporting.
Follow-up
- Two bindings land in the same wave but hit the same client endpoint, whose rate budget is shared. How does the wave plan have to change, and what problem class does that turn into?
- One dependency holds only for overlapping watermark ranges rather than globally. Is the dependency graph still a DAG over bindings, and what is the right vertex if not?
- Report the critical path so an operator can see which chain sets the wave count, not just that it is five.
Compute the minimal re-run set that preserves idempotency keys
One connector binding must cover [span_start, span_end). You have all its connector_run rows: (idempotency_key, attempt_no, status, watermark_from, watermark_to), with half-open ranges, where idempotency_key is derived deterministically from the connector and the range, so re-running a range reproduces its key. Only status='succeeded' counts as covered. Produce the minimal re-run plan: every instant in the span ends up covered, any sub-interval some prior run already claimed is re-run at exactly that claim's range, and genuinely unclaimed sub-intervals become new claims cut at the binding's chunk size. Explain what merging ranges would cost.
Approach
- Merge the succeeded ranges into a maximal disjoint union: sort by start, sweep extending the open segment while the next start is not beyond the current end. O(n log n) time, O(n) space, and this is the only status that contributes coverage.
- Take the complement of that union inside [span_start, span_end) to get the uncovered gaps, in O(u) over the merged segments. Clip at both ends so the plan never reaches outside the span.
- Sweep the non-succeeded claims (ambiguous, failed, abandoned) against the gap list with a two-pointer over both sorted lists. Any claim intersecting a gap is emitted at its original watermark_from and watermark_to, byte for byte, so the derived idempotency_key reproduces and the retry rejoins the same ledger row. O(n) after sorting.
- Subtract the emitted claim ranges from the gaps; whatever survives was never claimed by any attempt and becomes fresh ranges cut at the chunk size. Only these get new keys. O(u + c).
- Accept that a re-emitted claim may overlap time that is already succeeded. Range bookkeeping is not what makes the retry safe; the record-level dedupe key is, and re-reading a covered window costs one extra fetch against the client's quota, which is far cheaper than a key you can no longer reproduce.
- Exclude anything still in status 'running' with a live lease. Re-claiming it races the in-flight attempt, doubles the quota spend against the client endpoint, and produces two ledger writers for one key. Total plan cost is O(n log n) time and O(n) space.
Follow-up
- An ambiguous run may have applied every record before the timeout. What do you read to settle it before re-running, and what is left if the client's schema offers no natural key?
- Retention pruned the ledger rows for one of these keys. What does the re-run do now, and what bounds how long the ledger must be kept?
- Two planners run concurrently against the same binding. What stops them emitting and executing the same plan twice?
Fix a billing report that double-counts through a join
A monthly report joins engagement to work_record (engagement_id, principal_id, work_date, minutes, is_billable, status) and to invoice_line (engagement_id, period_start, period_end, amount_minor, line_type, status) in a single FROM clause, then aggregates sum(minutes), count(DISTINCT principal_id) and sum(amount_minor) grouped by engagement. Finance reports the minutes are roughly triple the timesheets while the headcount column looks correct. Explain the arithmetic, then write the corrected query for one client and one period, returning zero rather than NULL for engagements with no work.
Approach
- Name the arithmetic before touching SQL: joining two independent one-to-many children of the same parent produces the Cartesian product per engagement, |W| x |L| rows. sum(minutes) is therefore multiplied by the invoice-line count and sum(amount_minor) by the work-record count. Three lines per engagement — fees, expense, credit note — is exactly the factor of three finance reported.
- Explain why the headcount column looked fine: count(DISTINCT principal_id) collapses the duplication, so it is correct by accident. That is precisely why the bug survived review, and it is the reason a column agreeing with expectations is not evidence that a join is at the right grain.
- Pre-aggregate each branch to the join grain first: one subquery per fact table, grouped by engagement_id, each producing at most one row per engagement. Join those results, which are now one-to-one, so no multiplication is possible by construction rather than by care.
- Drive the outer query from engagement with LEFT JOINs and wrap each aggregate in COALESCE, because sum over zero rows is NULL rather than 0. For counts, count the key column and not count(*), which returns 1 for a non-matching LEFT JOIN row.
- Keep credit notes inside the amount sum deliberately: amount_minor is negative for line_type = 'credit_note', so filtering them out overstates what was billed. Exclude status = 'void' instead, and state that choice in the query as a comment because the next reader will assume the opposite.
- Compare cost: the fan-out plan materialises |W| x |L| intermediate rows before aggregating, while pre-aggregation reads each table once under its own index and hash-joins two small results. The base-table I/O is identical; the intermediate work differs by orders of magnitude on a large engagement.
Follow-up
- The report now needs a per-principal breakdown alongside the per-engagement totals. Does your shape survive, or do you need a different grain entirely?
- How do you stop the next analyst reintroducing this — a view, a naming convention, or a test that fails on a fixture with two children?
- Why does sum(DISTINCT minutes) not fix it, and what does it actually compute?
Add a non-null column to a live work-record table
work_record holds roughly nine million rows and absorbs a month-end write burst. You must add currency CHAR(3) NOT NULL, backfill it from the engagement, add a foreign key to rate_card, and eventually drop the superseded hours_decimal column. Several releases are live, including client-managed installs running two-quarter-old code that still writes hours_decimal. Give the ordered plan, the lock each step takes and for roughly how long, how the backfill is chunked and restarted, and the signal that makes the drop safe.
Approach
- Sequence it expand, dual-write, backfill, switch reads, contract, and be explicit that only the last step is dangerous. Adding is cheap because an old release ignores a column it does not know about; removing breaks a writer you cannot upgrade and whose failure you may not see for weeks.
- ALTER TABLE work_record ADD COLUMN currency CHAR(3) — nullable, no default. That is a catalog-only change on every supported version and takes milliseconds. Refuse NOT NULL DEFAULT 'XXX': PostgreSQL 11 and later store a non-volatile default in the catalog and materialise it on read, so it is just as fast, but it hands all nine million rows a value that is already non-null and already wrong. That destroys the rest of the plan — the NOT NULL you add later is satisfied from the first second, so it can never find a row the backfill missed, and every consumer now has to distinguish a real currency from a sentinel forever. A NULL is the only marker that survives a crashed backfill.
- The statement is instant; the lock queue is not. An ACCESS EXCLUSIVE request waits behind every open transaction holding a conflicting lock on the table, and every request arriving after it queues behind that, so one long analytics SELECT turns instant DDL into a total stall. Run it with SET lock_timeout = '2s' inside a retry loop that catches 55P03 and backs off with jitter.
- Decide up front what an unconverged writer inserts. Two-quarter-old code omits currency, so it writes NULL, which means SET NOT NULL would start rejecting its inserts outright. Either fill the gap in the database with a BEFORE INSERT trigger that resolves currency from the engagement when NEW.currency IS NULL — one indexed lookup per insert, which you should time against the month-end burst before committing to it — or leave the column nullable and gate SET NOT NULL on the same release-convergence signal that gates the drop. What you must not do is paper over it with a default, which turns a visible NULL from an unconverged writer into an invisible 'XXX' that reconciliation will never flag.
- Backfill on a keyset cursor — WHERE work_record_id > $last AND currency IS NULL ORDER BY work_record_id LIMIT 5000 — one transaction per batch, advancing $last to the last work_record_id the batch wrote so filled rows are never revisited. Set statement_timeout, pause between batches so autovacuum and any replicas keep up, and persist the cursor so a crash resumes from the last committed batch. OFFSET n costs O(n) per page because the server scans and discards those rows, which makes the whole backfill quadratic in the table size.
- Take NOT NULL without a blocking scan, and only once the backfill reports zero rows remaining: ADD CONSTRAINT work_record_currency_nn CHECK (currency IS NOT NULL) NOT VALID takes a brief ACCESS EXCLUSIVE and scans nothing; VALIDATE CONSTRAINT runs under SHARE UPDATE EXCLUSIVE so reads and writes continue throughout, and raises check_violation, SQLSTATE 23514, naming a row if the backfill missed one — that failure is the entire reason the step exists, and it only exists because the column was added without a default; then SET NOT NULL, which on PostgreSQL 12 and later proves itself from the validated CHECK and skips its own full scan.
- Use the same shape for the foreign key — ADD CONSTRAINT ... REFERENCES rate_card(...) NOT VALID, then VALIDATE CONSTRAINT — and build any supporting index with CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block, scans the table twice, and on failure leaves an INVALID index that must be dropped concurrently before you retry. Note what the FK does not prove: a single-column reference is MATCH SIMPLE, so rows with NULL currency pass validation untouched, and validating it before the backfill finishes certifies only the rows that were already correct.
- Gate the contract step on observed convergence rather than elapsed time: every non-destroyed environment must report a reported_release_id at or past the release that stopped writing hours_decimal, and reported_release_id IS NULL counts as unknown and therefore blocks. DROP COLUMN itself is a catalog-only update, instant under ACCESS EXCLUSIVE, reclaiming no space until a rewrite — the cost is entirely in the writer you did not know was still running.
Worked solution 45 min
- Write out each DDL statement in order, annotated with its lock mode and whether it is safe during the month-end burst.
- Implement the retry wrapper: SET lock_timeout, attempt, catch 55P03, sleep with jitter, retry a bounded number of times.
- Write the keyset backfill loop with the cursor persisted after each committed batch, and time one batch.
- Leave one row un-backfilled on purpose, run VALIDATE CONSTRAINT, and read the 23514 it raises — then fix that row and re-run. A plan in which this step cannot fail is a plan that is not checking anything.
- Write the convergence query over environment that returns the rows still blocking the drop.
- Ship the DROP COLUMN as its own migration whose precondition is that query returning zero rows.
Follow-up
- A client-managed install reports a release you no longer support and has not converged for two quarters. Do you drop, and what do you owe that client first?
- During the dual-write window, which column does the month-end reconciliation read, and what does a mismatch between the two mean?
- The backfill dies at batch 900 of 1,800. What exactly does the restart re-read, and how do you know it did not skip a range?
What is your strategy for maintaining code quality during a rapid deve…
What is your strategy for maintaining code quality during a rapid development cycle?
Approach
- Name the failure you are designing for, then the recovery path.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
How do you decide between building a custom solution versus using an e…
How do you decide between building a custom solution versus using an existing library or framework?
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
Can you explain the performance benefits of Server-Side Rendering (SSR…
Can you explain the performance benefits of Server-Side Rendering (SSR) in NextJS?
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
How do you manage state transitions in a complex ReactJS application?
How do you manage state transitions in a complex ReactJS application?
Approach
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Keep delivery running when the control services go unreachable
A network partition cuts one region's workers off from the Engagement and Entitlement Service and the Access Broker for 90 minutes. Residency forbids failing over to another region's copy of that data. Inside the region, connector runs hold cached credentials with not_after values minutes to hours out, and operator sessions are mid-task. The entitlement service fails closed by design. Decide what continues and what stops, give a stated exposure bound for each choice, and name the one thing that must never continue however long the partition lasts.
Approach
- Classify each operation by whether it widens authority, because that is what decides the answer. Continuing on a credential already minted extends access that was already granted; minting a new one grants access you currently cannot verify. A blanket fail-closed that stops both is as unconsidered as a blanket carry-on, and neither tells you what your exposure was afterwards.
- Let in-flight work continue on already-issued credentials. Their exposure is already bounded by not_after, which is the reason you capped it at issue, and the worst case is the longest outstanding not_after at the moment the partition began. That number is not fate: capping connector TTLs at the expected run duration rather than at the engagement window shrinks it in advance, which is the only time it can be shrunk.
- Refuse everything that widens authority - new mints, scope increases, the first run of a binding that has never run, an operator establishing new access. Return those as readable refusals naming the cause rather than as transport errors, or every operator and automated agent in the region spends 90 minutes guessing whether the system is broken or is saying no.
- Name the operation that must never continue: work against an engagement whose ends_on has already passed. Expiry is arithmetic over data every worker already holds and needs no network, so a partition is no excuse for it. This is the concrete payoff of never materialising an active flag - the one check that has to survive isolation is the one that is pure computation on a date.
- Time-box the degradation by construction: refuse to serve from a snapshot older than the staleness bound, so the window ends on its own rather than when somebody notices. On reconnect, reconcile rather than resume - re-read entitlements, revoke grants for engagements that closed during the window, and resolve runs that ended ambiguous against their dedupe keys, since a partition is the single most likely producer of ambiguous outcomes.
- State the cost plainly. This chooses availability for already-authorised work and consistency for every change of authority, and pays with a window in which a closure decided elsewhere is not yet honoured. That window is min(longest remaining TTL, staleness bound), it is a number you can put in front of a client, and it is shorter than the partition.
Worked solution 45 min
- Write the region's operation inventory: for each operation, whether it widens authority, what it reads, and whether that read can be served from cached state.
- For each one, state the exposure if it proceeds on data 90 minutes stale, as a duration and a scope rather than as a risk rating.
- Simulate the partition with a firewall rule and run two bindings: one whose credential has 40 minutes left, one whose engagement ended yesterday.
- Restore connectivity and run the reconciliation, recording which grants are revoked, which runs are reclassified, and which watermarks are re-checked.
Follow-up
- An engagement is terminated for cause during the partition. What is the real access window, and what would you change afterwards to shorten it?
- Half the region's workers can reach the entitlement service and half cannot. What breaks in that case that a total partition would not have broken?
- Your staleness bound is 10 seconds and the partition lasts 90 minutes, so delivery stops entirely. Is that bound right, and what evidence would change it?
Revenue dashboard and issued invoices disagree for one region
Finance reports that the monthly revenue dashboard runs 0.3 to 1.2 percent below the invoiced total for one region every month, and that the gap is largest when the month ends on a Friday or Saturday. The invoice generator selects approved work_record rows with work_date between period_start and period_end. The dashboard aggregates the same table with date_trunc('month', entered_at) inside a separate reporting job. Say which figure is correct, name both defects, and give the fix.
Approach
- Reconcile rows, not totals. For one engagement and one month, take the set difference between the work_record_ids the generator selects and the ids the dashboard aggregates. A percentage gap is unarguable; a list of discrepant rows names the defect in a minute.
- Read the columns on the discrepant rows. They will share one pattern: work_date inside the period, entered_at after it. That identifies the first defect as column choice. entered_at is when somebody filled in the timesheet, routinely days later by design, so end-of-month work lands in the following bucket. The weekday correlation is the confirmation, because a month ending Friday or Saturday pushes more entries into Monday.
- Fix the column and reconcile again, expecting a residual for one region. date_trunc('month', x) on a timestamptz truncates in the session TimeZone setting; the reporting job runs with TimeZone = UTC while that region's teams work at UTC+10, so entries made in their local morning on the first carry a UTC instant on the previous day. The same applies to any timestamptz cast to date.
- Demonstrate rather than assert: run the same date_trunc over the same row with SET TimeZone = 'UTC' and then with the region's zone, and show the two buckets differ. That is the discriminating observation, and it takes one query.
- Fix by removing the ambiguity, not by compensating for it. Aggregate on work_date, which is a DATE and has no zone to be wrong about, and make it the only period key for both billing and reporting. Where a timestamptz genuinely must be bucketed, such as entry latency or an SLA timer, state the zone explicitly via date_trunc's three-argument form on PostgreSQL 16 or later, or an explicit AT TIME ZONE, and never rely on the session default.
- State the answer plainly: the invoice is correct and the dashboard is wrong. Do not reconcile by adjusting invoiced rows, which are immutable once locked.
Follow-up
- Finance asks for a restatement of the last twelve months. What is safe to restate, given that invoiced work is immutable and corrections are credit notes?
- Someone proposes storing work_date as a timestamptz for consistency with the rest of the schema. Argue the case either way.
- What test fails the build if a future reporting job reintroduces session-dependent bucketing?
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 ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
For anything that touched live traffic, be ready to say how you would have undone it: a flag, a staged rollout, dual writes with the old path still authoritative. Once the old column is dropped or the source rows are overwritten there is no reverse, so name what you kept a copy of and for how long.
How do you handle technical debt when working under tight project dead…
How do you handle technical debt when working under tight project deadlines?
Approach
- Close with what you would do differently, concretely.
- 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.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Describe a time you had to optimize a piece of code that was causing s…
Describe a time you had to optimize a piece of code that was causing system latency.
Approach
- Close with what you would do differently, concretely.
- 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
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Unblock an engineer stuck on an intermittent connector
An engineer has spent two days on a connector that intermittently applies nothing: some runs read records and apply zero, others are clean, and they have been adjusting retry settings between runs. Describe how you have actually unblocked someone in this position. The account should show what you asked before you suggested anything, the method you handed over rather than the fix you found, and how you checked a week later that the method stuck rather than just the one bug.
Approach
- Start with what you asked, not what you knew: which failure classes those runs carry, whether records_read and records_applied differ and by how much, and whether the observed source schema version changed. Those three separate a dedupe hit from a rejected write from a client-side throttle.
- Hand over the habit rather than the answer: write the candidate causes down (credential expiry mid-run, schema drift failing validation quietly, the client's gateway returning 200 with an empty body, dedupe suppressing everything after a key derivation change), then pick the single observation that eliminates each.
- Name what you deliberately did not do, which is take the keyboard, and be honest that this is slower in the moment.
- Say how you verified the method transferred: a later, unrelated failure they classified before changing anything, or a runbook they wrote that somebody else used.
- Mention the structural change the episode justified, such as recording failure_class by owner so that 'intermittent' becomes a bucket with a count rather than an adjective.
Follow-up
- Same engineer, but the client's change window closes in three hours. What changes, and what do you give up by changing it?
- How do you distinguish someone who is stuck from someone who is struggling productively, before you intervene?
- 01
How do you handle technical debt when working under tight project deadlines?
- 02
Describe a time you had to optimize a piece of code that was causing system latency.
- 03
An engineer has spent two days on a connector that intermittently applies nothing: some runs read records and apply zero, others are clean, and they have been adjusting retry settings between runs. Describe how you have actually unblocked someone in this position. The account should show what you asked before you suggested anything, the method you handed over rather than the fix you found, and how you checked a week later that the method stuck rather than just the one bug.
Is this an official Stellar Solutions interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Stellar Solutions. Rounds and questions reflect what candidates have reported, not a process Stellar Solutions 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 most candidates complete the cycle within a few weeks. The process aims to move efficiently while gathering enough information for an informed decision.
PracHub interview research ↗What differentiates a top-tier candidate?
Successful candidates demonstrate a deep understanding of the "why" behind their technical choices. They show a high degree of ownership and the ability to communicate technical trade-offs clearly.
PracHub interview research ↗Is there a focus on whiteboard coding?
While technical problem-solving is assessed, the interviews tend to focus on practical, real-world scenarios that relate to the work you would actually do in the role.
PracHub interview research ↗How can I prepare for the cultural fit portion?
Focus on being authentic. Interviewers look for individuals who are collaborative, curious, and committed to continuous improvement.
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