As a Software Engineer at Alithya, you are a critical contributor to the success of complex digital transformation projects. Alithya operates as a global strategy and digital technology consulting firm, meaning your work directly impacts the business outcomes of a diverse range of clients. You will often work within cross-functional teams, bridging the gap between high-level client objectives and the technical implementation of scalable, robust software solutions.
This role is both challenging and dynamic, often requiring you to operate in a consulting capacity. You will contribute to various stages of the software development lifecycle, from technical design to implementation and delivery. Because Alithya serves a wide array of industries, you may find yourself working with modern tech stacks like Java, Spring Boot, Angular, or React, depending on the specific client engagement or internal project you are assigned to.
Success in this role requires more than just technical proficiency; it demands adaptability. You must be able to communicate effectively with both technical peers and non-technical stakeholders, often navigating the balance between established best practices and the specific, sometimes unique, requirements of a client’s environment.
Recruiter Screen
reportedHalf of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.
What to demonstrate
- Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
- Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
- Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not
How to prepare
- Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
- Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
- If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window
Technical Assessments
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.
Interviews with Leads/Clients
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
PracHub editorial advice for the preparation topics above.
Tenant context leaking across a connection pool
Setting the tenant on a connection and relying on it for the rest of the request looks correct in every test that runs one request at a time. Under transaction pooling the connection goes back to the pool with that session state still attached, and the next checkout, serving a different client, inherits it; row-level security then enforces the previous tenant's policy flawlessly and returns the wrong rows. It reproduces only under concurrency, which is the load profile your integration tests do not have. Use a transaction-scoped setting, and assert that the setting matches the request's tenant immediately before the first query rather than trusting that it was set correctly upstream.
Validating against a client sandbox and assuming production parity
Sandboxes typically carry smaller data, looser or absent rate limits, a schema version behind production, and sometimes synchronous behaviour where production is asynchronous. Code that passes there fails first in the client's production, during a change window you do not control and often cannot get a second one of. The mitigations are specific: assert the observed schema version at the boundary of every run, measure the production rate limit empirically rather than reading it from a document, and design the first production run to be a bounded, reversible slice rather than a full backfill.
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.
Assuming the input fits in memory
Ask how large the input is in bytes before committing to an in-memory algorithm; beyond that point the options are a single streaming pass, an external sort with bounded buffers, or a sketch that trades exactness for constant memory. An algorithm that assumes random access to the whole input is a different algorithm from one that sees each element once.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Present a technical project you have worked on and explain your design…
Present a technical project you have worked on and explain your design choices.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Implement the quadratic equation in your language of choice using OOP …
Implement the quadratic equation in your language of choice using OOP principles.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Collapse connector retry attempts into logical jobs and failure counts
You are given one UTC day of connector_run rows: (connector_id, idempotency_key, attempt_no, status, failure_class, records_read, records_applied), unique on (connector_id, idempotency_key, attempt_no), up to 5,000,000 rows in arbitrary order. Retries of one logical job share the idempotency_key; the job's outcome is the status of its highest attempt_no. Return, per connector_id: the number of logical jobs, a count of terminally failed jobs by failure_class, and the dedupe hit total, meaning sum(records_read - records_applied) over terminal-succeeded attempts only. One pass over the input; state your memory bound.
Approach
- Key a hash map on (connector_id, idempotency_key) and keep only the highest attempt_no seen so far plus that row's status, failure_class and record counts. One pass, O(n) time, O(d) space where d is the number of distinct logical jobs, not O(n).
- Treat a repeated (key, attempt_no) as corrupt input and raise, since the table's uniqueness constraint says it cannot happen; silently overwriting hides a double-insert in the runtime.
- Fold the d surviving entries into per-connector counters in a second pass over the map, not over the rows. The failure_class histogram is a small fixed-width map per connector because failure_class is an enum.
- Compute dedupe hits only from terminal-succeeded attempts. A failed attempt's records_read is work attempted, not work deduplicated, and adding it counts the same source records once per retry.
- If d does not fit in memory, partition the input by hash(connector_id, idempotency_key) into p files and aggregate each partition independently. Same O(n) total work, p sequential passes, memory traded for I/O; the partition function must use the full key or a job's attempts split across files.
Worked solution 20 min
- Take six rows: (c=7,K=A,1,failed,client_429,0,0), (c=7,K=A,2,succeeded,null,500,480), (c=7,K=B,1,ambiguous,client_timeout,300,300), (c=7,K=B,2,succeeded,null,300,0), (c=9,K=C,1,failed,schema_mismatch,0,0), (c=9,K=C,2,failed,schema_mismatch,0,0).
- Build the map: A -> attempt 2 succeeded, B -> attempt 2 succeeded, C -> attempt 2 failed/schema_mismatch. Three entries from six rows.
- Fold per connector: connector 7 has 2 logical jobs, 0 terminal failures; connector 9 has 1 logical job, 1 terminal failure classed schema_mismatch.
- Dedupe hits from terminal-succeeded attempts only: (500-480) + (300-0) = 320 for connector 7, 0 for connector 9.
- Compute the wrong answer deliberately by counting attempt rows: 6 jobs and 3 failures, which is what a naive GROUP BY on run rows returns.
Follow-up
- A job's highest attempt is 'cancelled' but an earlier attempt succeeded. What is the job's outcome, and what does that say about who writes the cancel?
- Produce this incrementally as runs land instead of as a daily batch: what state do you keep per key, and what happens when an attempt arrives out of order?
- records_applied is written by your runtime, not by the client. What would you reconcile it against before anyone trusts the dedupe-hit number?
Generate invoice lines once under concurrent month-end runs
Month-end generation reads approved work_record rows for one engagement and period, sums minutes, inserts one invoice_line whose generation_key is UNIQUE over (engagement_id, period_start, period_end, line_type, generator_version), then sets those work records to invoiced with locked_at and invoice_line_id. Two runs execute concurrently for the same engagement while late approvals are still committing. Working in PostgreSQL, name the anomaly at READ COMMITTED and at REPEATABLE READ, and specify the controls that make a retry a no-op rather than a second charge.
Approach
- At READ COMMITTED each statement takes a fresh snapshot, so the SELECT that computed the sum and the later UPDATE see different data: an approval committed between them is a phantom the sum missed but the UPDATE can still mark invoiced. Worse, an UPDATE that blocks on a row another transaction is changing re-evaluates its WHERE against the new row version once the lock releases, so WHERE status = 'approved' silently skips rows the other run already moved — fewer rows than you counted, and no error anywhere.
- At REPEATABLE READ, which in PostgreSQL is snapshot isolation, the sum and the update agree because the whole transaction shares one snapshot, but a concurrent update to the same row aborts you with serialization_failure, SQLSTATE 40001, which the application must catch and retry. Snapshot isolation still permits write skew across different rows; only SERIALIZABLE with SSI excludes it, at the cost of more 40001s and predicate-lock memory bounded by max_pred_locks_per_transaction.
- Make the second run collide instead of race: take a transaction-scoped advisory lock on a hash of (engagement_id, period_start, line_type) at the top of the run, so one generates while the other waits and then finds finished work. Keep the UNIQUE generation_key as the backstop, because the advisory lock is per-database-connection state that a failover or a second database node does not carry.
- Treat the unique violation as success. Catch SQLSTATE 23505, re-select the existing invoice_line by generation_key, and return it. INSERT ... ON CONFLICT DO NOTHING ... RETURNING returns zero rows on conflict, so a handler that trusts RETURNING writes a NULL invoice_line_id and reports a failure for work that in fact completed; ON CONFLICT DO UPDATE is worse, because it overwrites a line that has already been issued.
- Bound the read deterministically rather than by timing: select the work records FOR UPDATE ordered by work_record_id so both runs acquire row locks in the same order and cannot deadlock, and filter on approved_at < the run's start timestamp so a late approval is out of scope by definition and lands in the next period.
- Enforce immutability in the database, not in the service. A BEFORE UPDATE trigger raising when locked_at IS NOT NULL and a billed column changes, plus CHECK (status <> 'invoiced' OR invoice_line_id IS NOT NULL), means the constant and legitimate business pressure to edit an invoiced entry resolves into an appended credit_note row instead of a quiet update.
Follow-up
- A line has been issued and then an approval behind it is reversed. What rows do you write, and what does the original line look like afterwards?
- How many times does the run retry before giving up, and what state does it leave behind for the operator who picks it up?
- After the fact, how do you prove no engagement-period was billed twice, without trusting the application code that wrote it?
Return the latest attempt per connector idempotency key
connector_run stores one row per attempt: run_id, connector_id, idempotency_key, attempt_no SMALLINT, status (queued, running, succeeded, failed, ambiguous, cancelled, abandoned), failure_class, watermark_from, watermark_to, started_at, finished_at, unique on (connector_id, idempotency_key, attempt_no). For one connector_id, write the query returning the highest-numbered attempt per idempotency_key, keeping only those keys whose latest attempt is not succeeded, ordered by watermark_from. Give the index that serves it, and say what the plan does without that index.
Approach
- Use DISTINCT ON (idempotency_key) with ORDER BY idempotency_key, attempt_no DESC. PostgreSQL requires the ORDER BY to lead with exactly the DISTINCT ON expressions, and that requirement is what makes the row picked deterministic rather than whichever the scan happened to reach first.
- Apply the not-succeeded filter outside, in a wrapping SELECT. Putting it in the inner WHERE changes the question from 'keys whose latest attempt failed' to 'keys that have any non-succeeded attempt', which reports a key that failed twice and then succeeded, and is the single most common wrong answer here.
- Know the portable alternative: row_number() OVER (PARTITION BY idempotency_key ORDER BY attempt_no DESC) in a subquery, filtered = 1 outside. A window function cannot be filtered in WHERE because WHERE is evaluated before the window, and PostgreSQL has no QUALIFY clause, so the subquery is mandatory rather than stylistic.
- Index (connector_id, idempotency_key, attempt_no DESC). With connector_id fixed by equality, the index supplies the exact order DISTINCT ON needs and the Sort node disappears. It does not reduce rows read: through PostgreSQL 17 there is no loose index scan, so the executor still walks every attempt in range rather than skipping to each next distinct key.
- Give the complexity honestly: without the index the plan is a scan plus an O(n log n) sort over the connector's attempts; with it, O(n) ordered reads and constant working memory, where n is attempts in range, not distinct keys. If the distinct keys are a tiny fraction of attempts, a recursive skip-scan is the next step.
Worked solution 15 min
- Create the table with a handful of rows across three idempotency keys, including one key with attempts 1 failed, 2 ambiguous, 3 succeeded.
- Write the inner DISTINCT ON query alone and confirm it returns one row per key.
- Wrap it and add WHERE status <> 'succeeded' plus ORDER BY watermark_from in the outer query.
- Create the index and compare EXPLAIN output before and after.
Follow-up
- attempt_no is not monotonic in time because a retry was queued out of order. Does your query still pick the right row, and which column should it actually order by?
- How would you return the latest attempt per key across all connectors in one environment without the query degrading to a full sort?
- An ambiguous latest attempt means the target may or may not have been written. What does this query feed, and should ambiguous be treated as failed here?
Explain the four main concepts of Object-Oriented Programming (OOP) in…
Explain the four main concepts of Object-Oriented Programming (OOP) in Java.
Approach
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Can you explain SOLID principles and how you have applied them in past…
Can you explain SOLID principles and how you have applied them in past projects?
Approach
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
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?
Reports intermittently return another client's rows under load
A shared-pool deployment scopes every query with row-level security: the application runs SET app.client_id at the start of each request, and policies compare client_id against current_setting('app.client_id'). Twice this week a client's report contained rows belonging to a different client. Staging cannot reproduce it, single-request replays are clean, and the SQL text is identical in the good and bad cases. Connections come from a pool shared by all requests. Give the ordered diagnosis, the reproduction, and the fix.
Approach
- Order the hypotheses by what the evidence already excludes. Identical SQL text and clean single-request replays rule out a missing predicate in that endpoint, so the variable is not the query, it is the value the policy reads when the query runs. Check, in this order: is RLS enabled and forced on the table; is the serving role actually subject to it; and only then, what does current_setting return at execution time.
- Confirm the policy is in force before blaming it. Policies do not apply to the table owner unless FORCE ROW LEVEL SECURITY is set, and never apply to a role with BYPASSRLS or to a superuser. If traffic is served by the owning role, the policy has been decorative all along and the leak is not intermittent at all, only intermittently noticed.
- Instrument the value, not the code. As the first statement inside every transaction, read current_setting('app.client_id', true) and compare it with the tenant on the request, emitting a counter on mismatch. A defect invisible to replays shows up here within minutes of real concurrent traffic.
- Explain why pooling makes it intermittent. A plain SET is session-scoped and survives COMMIT, so the backend returns to the pool carrying the previous request's value. This needs no transaction-pooling proxy; an ordinary application connection pool is sufficient. The next checkout that sets the value late, or skips it on some path, inherits the previous tenant, and row-level security then filters flawlessly for the wrong client.
- Reproduce deterministically rather than by load-testing and hoping. Cap the pool at one connection, issue two requests for different tenants back to back, and have the second issue its first query before its SET. Zero rows would be the fail-closed outcome; the other tenant's rows is the leak, and the difference between those two results is exactly whether the previous value was left behind.
- Fix in layers. Use set_config('app.client_id', $1, true) or SET LOCAL inside the transaction so the value dies at COMMIT; assert it matches the request immediately before the first query rather than trusting an upstream middleware; set FORCE ROW LEVEL SECURITY; and serve traffic with a role that neither owns the tables nor holds BYPASSRLS, reserving the owning role for migrations.
Follow-up
- You owe a breach notification within contractual time. What query bounds which client pairs and which rows were exposed, and what in the schema makes that answerable at all?
- A background job has no inbound request and therefore no tenant. Where does its tenant come from, and what stops it from being where this happens next?
- How does the same failure mode look on the dedicated and client-managed tiers, and what does that tell you about where isolation should live?
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Numbers before diagrams
- Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
- Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
- Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.
Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One system, from requirements to schema
- Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
- Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
- Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.
Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.
Practice prompt ↗Practice prompt ↗03The consistency you are actually buying
- Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
- Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
- Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.
Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.
Practice prompt ↗Practice prompt ↗04Failure is the design
- For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
- Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
- Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
- Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.
Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Scaling the hot path
- Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
- Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
- Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
- Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.
Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.
Practice prompt ↗Practice prompt ↗06Keep the coding hand in, at the bar that applies to you
- Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
- Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
- Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.
Deliverable: One problem hardened to review standard, with its test list and one written review comment.
Practice prompt ↗07Defend it while being interrupted
- Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
- Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
- Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.
Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.
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.
Tell me about a challenging situation you faced in a project and how y…
Tell me about a challenging situation you faced in a project and how you handled it.
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- 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 professional qualities and areas you are looking to impr…
What are your professional qualities and areas you are looking to improve?
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Why do you want to work for Alithya?
Why do you want to work for Alithya?
Approach
- Name the disagreement and how you resolved it with evidence.
- 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?
- 01
Tell me about a challenging situation you faced in a project and how you handled it.
- 02
What are your professional qualities and areas you are looking to improve?
- 03
Why do you want to work for Alithya?
Is this an official Alithya interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Alithya. Rounds and questions reflect what candidates have reported, not a process Alithya has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long should I spend preparing for the technical assessment?
Dedicate enough time to refresh your knowledge of core data structures and your primary language's best practices. The assessments are designed to test your logic, so focus on accuracy and clean code rather than just speed.
PracHub interview research ↗What is the company culture like?
Alithya values a professional, collaborative environment. Since you will be working with clients, there is a strong emphasis on reliability, clear communication, and a "can-do" attitude toward solving complex business problems.
PracHub interview research ↗Will I be working remotely?
Work arrangements can vary based on the specific client or project. Always clarify the expectations for onsite or hybrid work during your initial recruiter screen.
PracHub interview research ↗What differentiates successful candidates?
Successful candidates don't just solve the problem; they explain their thought process throughout the coding exercise. Showing that you can communicate your technical decisions is just as important as the code itself.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-22