Interview guides · Akkodis Canada
Akkodis Canada Software Engineer Interview Guide
Akkodis Canada Software Engineer interview guide: reported questions on OOP, real-time and embedded systems, debugging, plus a 7-day prep plan.
The 60-second brief
Akkodis Canada is described as an engineering and R&D consultancy whose software engineers support clients in automotive, aerospace, energy and defense, often working inside the client's project environment. Domains mentioned range from real-time and embedded software to enterprise applications. Reported duties cover building, testing and shipping code, working with business managers and technical leads, joining technical reviews and peer feedback, and writing up processes and results so the client retains the knowledge.
This guide is for candidates interviewing for a Software Engineer position at Akkodis Canada. It covers the five reported questions (object-oriented programming, real-time timing constraints, comparing the protocols and tools on your resume, debugging a large or legacy codebase, and embedded or hardware-software integration), the project deep-dive format candidates describe, and a seven-day plan. Reports say exact questions vary by project or client, so the guide shows how to prepare when the posting names C++, Java or Python.
Reports mention a recruiter screen first, then conversations with managers or subject matter experts. A client interview sometimes comes last. No stage-by-stage format has been reported, so confirm it with your recruiter.
Focus areas
- Software Engineering (role fundamentals)
- Technical Interviewing
- Project Experience & Communication of Work
Overview
The reported questions are mostly experience questions rather than puzzles, with an emphasis on projects you have already done. That changes how you prepare: instead of grinding algorithm problems, build a small set of projects you can narrate in depth, with the architecture, the decisions, the trade-offs and what you would change.
The five reported questions cover object-oriented design, timing constraints in real-time software, the difference between protocols or tools listed on your resume, debugging in a large or legacy codebase, and embedded or hardware-software integration. Candidates also report project deep-dives such as walking through the architecture of your most complex project, and a code-level scenario about optimizing code for memory efficiency. For that scenario, work through a checklist: measure first with a profiler or allocation counter; avoid copies with const references and move semantics; call reserve() before filling vectors; prefer contiguous containers; check struct layout and padding; and keep heap allocation off the hot path. Interviewers may ask about anything you list, so cut or refresh any entry you cannot compare with an alternative.
The role description gives C++, Java and Python as example languages, with Git version control among the must-haves, and cloud platforms (AWS, Azure), CI/CD pipelines, real-time operating systems and tools such as MATLAB/Simulink as nice-to-haves. Candidates also report a domain-fundamentals area called Science of Materials. If you apply to client work in automotive or aerospace, review material properties and units that appear in requirements, and how such constraints turn into software limits and validation checks.
Because the work is client-facing, communication is part of the loop. Candidates report that the process can end with a client interview, that an English portion is possible, and that you may be asked to complete a candidate profile or reformat your CV. Practise explaining a technical decision to someone outside your field, and keep your own follow-up routine, since some candidates report slow feedback.
Where to focus your preparation
Candidates describe one general flow rather than a numbered set of rounds, so the section below treats it as a single preparation block and tells you what to confirm early.
Preparation focus
Reports describe a recruiter screen, then technical conversations with managers or subject matter experts, and sometimes a final interview with the client. Many candidates mention two to four weeks from first screen to offer, and some report gaps in feedback. No per-stage format, duration or coding-versus-discussion split has been reported, so ask the recruiter what each conversation covers and whether a technical test is part of your loop. Prepare for conversations driven by your resume: the languages and tools you list, a deep-dive on your most complex project, and the experience questions on OOP, real-time constraints, debugging and embedded work. Where the client takes part, learn the client's sector beforehand and be ready to explain technical choices in plain language. If a technical test appears, candidates advise treating it as a professional deliverable with clean code, documentation and tests.
What to demonstrate
- Reports say candidates are expected to explain why they chose each tool or language on their resume, not only how they used it
- Reports say candidates should be ready to walk through the architecture and decisions of a past project in detail
- Reports say candidates should convey technical ideas clearly to managers and non-technical stakeholders
- Software lifecycle understanding, from requirements to deployment, is reported as an area to be ready for
How to prepare
- Write a one-line reason and one contrast for every language, protocol and tool on your resume, since the reported question asks you to explain differences between them
- Build a project walkthrough for your most complex project: architecture sketch, two key decisions with trade-offs, one failure and what you changed
- Ask your recruiter which stages involve managers, subject matter experts or the client, whether code will be written, and whether work is remote or hybrid
- Prepare a short English walkthrough of one project, and have your CV ready in whichever profile format the recruiter requests
Where candidates lose points
Listing a protocol or tool on the resume that you cannot contrast with an alternative
For each entry, write a sentence on what it solved, one alternative you considered and why you picked this one. Cut anything you cannot defend in a follow-up, because the reported question asks you to explain differences between the items you listed.
Answering the object-oriented question with four textbook definitions and no project
Choose one project, name the real classes or interfaces, and tie each principle to a decision and a result. Include one place where inheritance was the wrong choice.
Claiming real-time experience but quoting only average latency
Know your deadline, your worst observed time under load and how you measured it. Be ready to explain priority inversion and what happens when a deadline is missed. If you lack real-time experience, say so and describe the nearest case.
Answering the debugging question with a guess or a fix before reproducing the problem
Lay out the sequence: reproduce, check what changed, bisect with Git history, add logging or a debugger at the boundary, then confirm with a regression test. For legacy code, add characterization tests before you refactor.
Treating a technical test as throwaway work, or going quiet after the screen
If a test appears, candidates advise treating it as a professional deliverable, so submit readable code with tests and documentation. Keep your own follow-up cadence with the recruiter, since some candidates report feedback gaps.
Questions & practice
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Diff a projection against the primary without per-row point reads
Hard · reconciliation · range hashing · throttling
The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.
Approach
- Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
- Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
- For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
- Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.
Follow-up
- The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
- Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?
Merge partitioned event streams into one ordered feed with bounded lateness
Hard · k-way merge · watermarks · out-of-order streams
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
Replace offset paging on the resource feed with keyset
Easy · keyset pagination · composite index · sort order
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
Approach
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
Follow-up
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Can you explain the difference between specific protocols or tools you have listed on your resume?
Medium · Technical & Domain Knowledge
Approach
- Before the interview, list every pair of protocols or tools on your CV that someone could ask you to compare. For each pair, know the property that decides between them: delivery guarantees, topology, addressing, duplex, throughput, overhead and failure mode. Answer in this shape: X does A, Y does B, so on my project I picked X because of constraint C.
- TCP vs UDP: TCP is connection-oriented and gives ordered, reliable delivery through sequence numbers, ACKs and retransmission, plus flow and congestion control. The cost is a handshake and head-of-line blocking. UDP sends independent datagrams with an 8-byte header and no delivery or ordering guarantee. DNS uses UDP because a single request and response does not need a handshake, and the client simply retries on timeout. Live audio or video and telemetry use it because a late packet is worthless.
- I2C vs SPI vs CAN: I2C has two open-drain lines (SDA, SCL) with pull-ups, addressed devices (usually 7-bit), a per-byte ACK and half-duplex transfers. SPI uses a clock, MOSI, MISO and one chip-select per peripheral. It is full-duplex with no addressing or built-in ACK, and usually faster. CAN is a multi-master differential bus where the lower identifier wins arbitration without destroying the frame. Classic CAN carries up to 8 data bytes per frame and CAN FD up to 64.
- For tools, compare their behaviour, not their feature lists. A Git merge creates a merge commit when the branches have diverged or when you pass --no-ff; a fast-forward merge only moves the branch pointer. Rebase rewrites commit hashes, so never rebase a branch others have pulled. C++ frees resources deterministically through RAII and destructors, while Java reclaims memory through garbage collection and needs try-with-resources for files and sockets.
- End each comparison with a trade-off you actually hit on a project and what you would choose now. If you only used a listed item briefly, say exactly what you did with it rather than claiming depth you cannot defend under a follow-up.
Follow-up
- You need to stream sensor samples at a high rate over a lossy link. Would you use TCP or UDP, and how would the receiver detect lost or reordered samples?
- Two devices on your I2C bus have the same fixed address. How do you get both working?
- What happens on a CAN bus when two nodes start transmitting at the same instant, and how does that affect latency for low-priority messages?
Keep one unresponsive destination from stalling all webhook delivery
Medium · fan-out · circuit breaker · timeouts
Egress delivery sends about 1.5k webhooks/second to 40k destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. One destination begins accepting connections and never responding; within the hour 150 destinations behave the same way. Design the delivery path so unrelated destinations are unaffected: the pool structure, the timeouts, the retry policy, the per-destination circuit, and what is recorded so a retry is not a second effect at the receiver. State how many in-flight slots the degraded destinations hold and why that number decides the design.
Approach
- Start with the number, and with the law that produces it. In-flight work is arrival rate times time in service, so 1.5k/second against a healthy 200 ms response needs about 300 concurrent slots. Per destination the same product applies, ceilinged by the concurrency cap: at the fleet average of 0.0375 deliveries/second per destination (1.5k spread over 40k) a 10-second timeout is 0.375 slots. A destination that has queued retries behind it is a different regime - every slot refills the instant an attempt expires, so it sits pinned at its cap of 4 - and 150 of those hold 600 slots, more than a pool sized for healthy traffic, entirely consumed by endpoints that will never answer. The per-destination cap bounds one endpoint and says nothing about the aggregate, which is exactly why it alone is not containment.
- Contain with bulkheads and an admission bound rather than a larger pool. Cap total in-flight per pool and shard destinations across pools by a hash of destination id, so a correlated group - one provider, one region - cannot exceed its pool's share. A delivery refused admission and re-queued with backoff is strictly better than one holding a slot on behalf of a receiver that is not listening.
- Treat the timeout as two timeouts, and be exact about what shortening one buys. Connect and read are separate failures and both must be shorter than the budget of whatever is waiting. Occupancy is min(cap, arrival rate x timeout), so dropping the read ceiling from 10 seconds to 3 cuts a merely slow destination's occupancy proportionally, 0.375 slots to 0.11 at the fleet average. It does not cut the 4 slots held by one of the 150: a destination with a retry backlog arrives far above cap/timeout - 0.4/second at a 10-second timeout, 1.33/second at 3 - so it stays pinned at the cap either way and only the slot-seconds per attempt fall. What that does buy is detection rate: 3.3x more failures observed per second on the same four slots, which is how fast the circuit reaches its threshold. Pick the value from the measured latency distribution of successful deliveries, with their high percentile as the floor, not from a round number.
- Add a circuit per destination, counting a timeout as a failure. Once open, fail fast without taking a slot - that is the whole point, converting 4 held slots into zero. Half-open on a schedule with exactly one probe and close only if the probe succeeds, so a permanently dead endpoint costs one request per interval instead of a growing retry queue.
Follow-up
- The destination is not dead - it answers in 9.5 seconds with a 200. Does a failure-rate circuit open? Should anything shed that traffic, and on what signal?
- One destination requires deliveries in order. What does a per-destination concurrency of 4 do to that guarantee, and what would you change to offer it?
How do you approach debugging complex issues within a large codebase or legacy system?
Medium · Technical & Domain Knowledge
Approach
- Reproduce first. Pin down the exact input, build, configuration, environment and how often it fails, then turn that into a failing test or script before changing any code. For an intermittent fault, add logging with timestamps and thread or task ids until you can tell a failing run from a good one.
- Narrow the search by bisection. Between a known-good and a known-bad version, git bisect finds the first bad commit in about log2(n) builds (roughly 10 for 1,000 commits). Without history, bisect the input data, the configuration flags or the code path with breakpoints and logging, so every step halves the space.
- Use the tool that fits the failure. For C++ crashes or corruption: a core dump plus a gdb backtrace, AddressSanitizer for buffer overflows and use-after-free, UndefinedBehaviorSanitizer, Valgrind. For Java: thread dumps (jstack) for deadlocks and heap dumps for leaks. Use git log and git blame to learn why the odd-looking code was written that way before you remove it.
- Separate the trigger from the cause. A recent change often only exposes a latent bug, such as an uninitialised variable, a data race or an off-by-one. On timing-sensitive or embedded code, print statements change the timing and can hide the bug. Use a ring-buffer trace, GPIO toggles on a logic analyser or hardware watchpoints instead.
- Fix it with a regression test written first. In legacy code with no tests, add characterisation tests that pin the current behaviour before the change. Keep the fix minimal, confirm it against the original reproduction, and write down the root cause so the next engineer on the system does not repeat the search.
Follow-up
- The bug appears only in the optimised release build, never in debug. What are your first suspects and how do you check each?
- It happens about once a week in production and you cannot reproduce it locally. What do you add to the system to catch the next occurrence?
- How do you change a large function with no tests without breaking callers you do not know about?
Suggested practice checklist
The plan follows the reported experience questions and the project deep-dive format: resume tools first, then OOP, real-time and embedded stories, legacy debugging, a technical-test rehearsal and a full mock. Practice-library questions are attached where they exercise memory-conscious coding.
Day 1 — Audit your resume and confirm the format
- List every language, protocol and tool on your CV. Beside each, write why you chose it and one alternative you could compare it with.
- Pick the five entries you are least sure of and write a three-sentence contrast for each, as the reported question about protocols and tools asks.
- Optional: if CI/CD, a cloud platform or MATLAB/Simulink appears on your CV, write one sentence each in the same reason-plus-contrast format.
- Email your recruiter to ask which stages involve the client, whether code will be written, and the remote or hybrid expectation.
Deliverable: A resume audit table with a reason and contrast per entry, five written contrasts, and a sent email to the recruiter.
Day 2 — Object-oriented design from your own project
- Sketch the class or interface diagram of one real project and mark where encapsulation, abstraction and polymorphism each appear.
- Write one example where composition beat inheritance, and one where a hierarchy needed flattening.
- Say the answer aloud using the language on your CV (C++ or Java), including its language-specific traps.
Deliverable: One annotated class diagram and a spoken answer to the OOP question, outlined in STAR form.
Day 3 — Project deep-dive and memory-aware code
- Write an architecture walkthrough of your most complex project: components, data flow, two decisions with trade-offs and one thing you would change.
- Map the project to the software lifecycle from requirements through deployment and note what you owned at each phase.
- Apply the memory checklist (measure, avoid copies, reserve(), contiguous containers, struct padding, no hot-path heap allocation) to a function from your own code and note what changed.
- Solve the practice question on merging partitioned event streams with bounded lateness, and state its memory use and why a heap keeps it small.
Deliverable: A one-page architecture walkthrough, a lifecycle mapping, a memory review of one function and one solved practice problem with its memory analysis.
Day 4 — Real-time and embedded stories
- Write the real-time answer: deadline type, budget, scheduling approach, how you measured the worst case, and the fallback on a miss.
- Write the embedded or hardware-software integration answer: system boundary, interface, one bring-up bug and how you isolated it.
- Practise explaining priority inversion and interrupt handling in two sentences each.
Deliverable: Two STAR outlines, one for timing constraints and one for hardware integration, plus two short spoken explanations.
Day 5 — Debugging a large or legacy codebase
- Write your debugging sequence as a checklist: reproduce, narrow what changed, bisect with Git, instrument the boundary, confirm with a regression test.
- Pick a real bug you fixed and outline it with that checklist, noting the wrong theories you discarded.
- Describe how you would make a change safely in code with no tests, using characterization tests first.
Deliverable: A debugging checklist and one real bug story outlined against it.
Day 6 — Treat a technical test as a deliverable
- Solve the practice question on finding overlapping job attempts and peak concurrency, writing it as you would submit it: clear names and unit tests for empty input, ties and nested intervals.
- Add a short README that states the approach, complexity and assumptions, as your own documentation habit.
- Make small, well-described Git commits as you go.
Deliverable: A tested solution with a README and a clean commit history.
Day 7 — Full mock and client-facing communication
- Run a mock covering the OOP, real-time and embedded questions plus the project walkthrough, speaking aloud and recording it.
- Explain one technical decision in plain language as if to a business manager, then repeat a short version in English.
- Write three questions about the project, team structure and professional development, and format your CV as the recruiter requests.
Deliverable: A recorded mock with notes on weak answers, a plain-language explanation, three questions to ask and a CV ready to send.
Project stories and client-facing communication
Several reported questions here are experience questions that you answer as stories: your OOP practice, timing constraints in real-time work and embedded integration. Candidates also report project deep-dives and discussion of adapting to different client environments. The last two prompts below are practice prompts based on those reported adaptability and communication themes, not reported questions. Prepare outlines in STAR form where the decision, the trade-off and a result you can verify take most of the time, and keep each story to one project you can be questioned on in detail.
- Walk me through the architecture of your most complex project.
- Practice prompt (based on the reported adaptability theme): Tell me about a time you joined a project or team with an unfamiliar domain and had to become productive quickly.
- Practice prompt (based on the reported communication theme): Describe a time you had to explain a technical decision to a non-technical stakeholder.
Can you describe your experience with Object-Oriented Programming principles and how you apply them in your pr
Medium · Technical & Domain Knowledge
Can you describe your experience with Object-Oriented Programming principles and how you apply them in your projects?
Approach
- Anchor the answer in one real project and name actual classes. Show each principle as a decision: encapsulation (what state you hid and which invariant that protects), abstraction (the interface callers depended on), polymorphism (where behaviour varied, such as a strategy object replacing a switch statement) and inheritance versus composition (why you picked one).
- Add language-level detail for the language on your CV. In C++ that means virtual destructors on polymorphic bases, object slicing, RAII and the rule of five. In Java it means interface versus abstract class, immutability, and keeping equals and hashCode consistent.
- Include one case where object orientation hurt: a deep inheritance chain you flattened into composition, or a class that grew several responsibilities and was split. A concrete SOLID example, such as depending on an interface so a component could be tested with a fake, shows judgement.
- Use STAR but spend little time on the situation. Close with the result you can verify: adding a variant touched one class, tests ran without the real dependency, or review comments dropped. The mistake that sinks it is reciting four textbook definitions with no project behind them.
Follow-up
- When would you choose composition over inheritance, and what did you choose in your project?
- What is the difference between an abstract class and an interface in the language you used?
- How did the design make the code easier to test or change later?
How do you handle real-time system requirements or timing constraints in your software design?
Medium · Technical & Domain Knowledge
Approach
- Begin by stating what kind of timing requirement your project had: hard (a missed deadline is a failure), firm (a late result has no value, but an occasional miss is tolerated) or soft (a late result still has some value at reduced quality), plus the actual deadline or latency budget. If you have not worked on real-time software, say so and describe the closest case, such as a latency budget in a service, rather than inventing experience.
- Describe the design techniques you applied: bounded work per cycle with no unbounded loops or blocking calls, memory allocated up front instead of on the hot path, short interrupt handlers that hand work to a task, fixed-priority preemptive scheduling with priorities assigned by period (rate-monotonic), and priority inheritance on shared locks to avoid priority inversion.
- Cover verification: measure worst-case behaviour, not averages, using timestamps, a toggled pin with a scope, or a trace tool. Report jitter and the longest observed time under load. Mention the fallback when a deadline is missed, such as a watchdog reset or a safe default output.
- Tell it as a story: the constraint, what you measured, the change and the number afterwards. The sinking mistake is saying the system was fast on the evidence of average latency alone.
Follow-up
- What is priority inversion, and how do you prevent it?
- How would you measure or bound worst-case execution time for a task?
- What does your design do when a deadline is missed?
What is your experience with embedded systems or specific hardware-software integration?
Medium · Technical & Domain Knowledge
Approach
- Open by drawing the boundary: the hardware (board, sensor, actuator or device), the software layer you owned, and the interface between them, such as registers, a driver API, a bus or a protocol. State who owned each side, since integration stories fall apart when your contribution is unclear.
- Give technical specifics from your project: memory-mapped registers and the volatile qualifier, interrupts versus polling, DMA where relevant, the bus you used (for example UART, SPI, I2C or CAN), byte order and struct packing in messages, and the RAM and flash limits that shaped your code. Name a hardware abstraction layer if you used one so you could test logic off-target.
- Walk through one bring-up or integration bug: the device behaved differently from the datasheet or timing diagram, and you isolated it with a logic analyzer, a scope, serial logging or a debugger, plus a known-good test to separate hardware fault from software fault.
- If your experience is limited, describe the closest honest equivalent, such as a device driver, a sensor API, or a model simulated before hardware existed, and say what you would study next. Do not claim hands-on board work you cannot discuss in detail.
Follow-up
- How did you test your code when the hardware was not available?
- How would you tell a hardware fault from a software bug on a board that misbehaves?
- Which memory, power or timing limits shaped your design?