cswork / libraryOpen practice workspace

Interview guides · Celestica

Celestica Software Engineer Interview Guide

Celestica Software Engineer interview guide: reported rounds, C/C++ and OS questions, troubleshooting scenarios, behavioral prompts and a 7-day plan.

The 60-second brief

Celestica is described as a company in design, manufacturing and supply chain solutions, working with enterprise, cloud, aerospace and communications infrastructure providers. The Software Engineer role sits where software meets that hardware work: candidates describe firmware and drivers for networking and switching hardware, automated test frameworks, embedded control units, diagnostic tools and factory automation applications. Day to day, the role is reported to mix low-level code, test utilities and backend application services, with unit tests, design documentation, architecture reviews and help diagnosing system-level failures alongside hardware designers, manufacturing process engineers and quality leads. Which of those areas you land in depends on the team, so read the posting for yours.

This guide is for candidates applying for a Software Engineer position at Celestica, whether the team is closer to embedded and test tooling or to enterprise application work. It covers the five reported rounds, preparation for the reported C/C++, data structure, operating system, hardware-interface, troubleshooting and behavioral questions, and a seven-day plan that works through them.

Candidates report five stages over roughly four to six weeks: an HR phone screening, technical evaluations, a technical written assessment (reported only in some locations), a practical project discussion and a final management assessment.

Focus areas

  • Problem Solving
  • Data Structures
  • Explain Your Thought Process

Overview

The reported questions cluster in four groups: C/C++ and data structure fundamentals (malloc versus calloc, inserting into a singly linked list, volatile, bit flipping, static versus dynamic linking), scenario-based problem solving (an intermittent failure, a frozen test suite, the defect escape rate, an assembly puzzle with no instructions), systems and hardware integration (interrupts and context switches, threads versus processes, TCP versus UDP, register and sensor-bus access, memory alignment), and resume and behavioral questions. Very little of it is algorithm-puzzle material. It is mostly explaining how things work under the language and how you would find a fault, so practise saying the mechanism out loud, not just recognising the term.

The requirements candidates describe for the role list C, C++ or Python, object-oriented design, memory management and pointers, arrays, linked lists, hash tables and trees with complexity analysis, plus threads, processes and basic networking. Nice-to-have items include I2C, SPI, UART, PCIe and Ethernet, debuggers such as GDB and JTAG, logic analyzers, LabVIEW or C# test frameworks, and basic SQL and Excel for log analysis. You do not need all of them, but anything of that kind on your resume is something to be able to discuss in depth.

Candidates also report that past projects get long discussions: your individual contribution, why you picked the language or architecture, which trade-offs you made between memory, speed and schedule, and how you tested. The troubleshooting questions reward a visible method, so answer with a list of hypotheses and the experiment that would separate them, and say what you would measure before you change anything. The plan below puts C and OS fundamentals first, then troubleshooting scenarios, then project and behavioral stories, and finishes with a full mock run of the five rounds.

The Celestica interview process

Candidates report five stages over roughly four to six weeks. The technical stage may be one round or two, and the written assessment is reported only in some locations, so confirm the exact sequence for your team with the recruiter.

HR Phone Screening

Candidates report this as a recruiter call about your background, your location preferences and your pay expectations, ahead of the technical stages. Treat it as a short, factual conversation: you are confirming that your experience fits the posting and that logistics line up. It is also the best moment to ask which later stages your team uses.

What to demonstrate

  • Whether your background matches the languages and areas in the posting
  • Location and work-arrangement fit
  • Pay expectations compared with the role

How to prepare

  • Prepare a two-minute account of your background that names the C, C++ or Python work and any embedded, test-automation or hardware-adjacent projects.
  • Decide your location constraints and pay expectation in advance so you can answer plainly.
  • Ask whether your location includes a written assessment and whether the technical stage is one round or two.

Technical Evaluations

Candidates report one or two technical and managerial evaluations, run by a panel that can include hiring managers, senior engineering leads and peer engineers, either virtually or on site. The reported question groups for technical panels are C/C++ and data structures, operating system and hardware integration, and scenario-based troubleshooting. Approach it by being ready to code small functions on request and to explain mechanisms aloud, naming the failure each concept prevents.

What to demonstrate

  • Command of C/C++ memory, pointers and bit manipulation
  • Data structure implementation, such as linked lists
  • Operating system basics: threads, processes, synchronization, interrupts
  • Ability to explain reasoning while working a problem

How to prepare

  • Write a singly linked list insert at a given position and an in-place reversal in C without running the compiler first, then test them by hand on empty, one-node and out-of-range cases.
  • Practise one-minute spoken answers for malloc versus calloc, volatile, memory alignment, and static versus dynamic linking, each with a tiny code or layout example.
  • Rehearse explaining how a data race occurs and how a mutex or atomic operation removes it.

Technical Written Assessment

Candidates report a short technical written assessment or coding quiz in some locations, covering fundamental concepts and analytical reasoning. Because it is not reported everywhere, check whether it applies to you. Prepare for it as writing under constraint: concise, correct definitions, small code snippets and reasoning on paper or in a plain editor, with no debugger to lean on.

What to demonstrate

  • Written accuracy on fundamentals such as bit operations, pointers and linking
  • Correct working code if the format is a coding quiz
  • Analytical reasoning set out clearly in text

How to prepare

  • Write answers by hand or in a bare text editor: flip, set, clear and test bit n; insert into a linked list; static versus dynamic linking in four sentences.
  • Give each written answer a definition, a short example and one pitfall, then trim it to what fits in a few lines.
  • Do a final read-through of every snippet for off-by-one errors, unsigned shifts and null pointers.

Practical Project Discussion

Candidates report a discussion of practical hardware-software interface challenges and scenario-based troubleshooting. Expect to reason about code that touches registers, sensors or test equipment, and about faults that are hard to reproduce. Approach it by choosing a method and saying it aloud: reproduce, narrow the layer, form hypotheses, run the cheapest discriminating test, then fix and prevent recurrence.

What to demonstrate

  • Structured root-cause analysis under incomplete information
  • Understanding of how software reads from and talks to hardware
  • Awareness of defect control and automated validation
  • Clarity in explaining decisions on a real project

How to prepare

  • Build a reusable checklist for an intermittent failure: capture inputs and timestamps, make the run reproducible, split software from firmware from hardware, and compare passing against failing runs.
  • Work out the steps for a frozen test suite with no exception: attach GDB or dump thread stacks, look for a lock or blocking read with no timeout, then check the instrument's state.
  • Prepare two projects with an architecture sketch, your own contribution, one trade-off and how you tested it.

Final Management Assessment

Candidates report a final assessment by management focused on overall fit and decision-making. Prepare for questions about prioritizing under deadline pressure, working with hardware, test and operations colleagues, and choosing between urgent fixes and longer-term cleanup. Bring decisions you owned, with the options you weighed and what happened afterwards.

What to demonstrate

  • Decision-making when fixes compete for limited time
  • Communication and collaboration across hardware, test and operations teams
  • Overall fit with the team and its schedule

How to prepare

  • Prepare a structured answer for ordering fixes ahead of a customer hardware release: severity, blast radius, workaround, risk of the change, and who must be told.
  • Rehearse the refactoring versus line-stop bug fix trade-off with a real example, including what you deferred and how you tracked it.
  • Write three questions about the team's deployment pipeline, test frameworks and hardware platform to ask at the end.

Where candidates lose points

Defining volatile, alignment or linking from memory without explaining what actually happens

For each concept, say what the compiler, linker or CPU does and give one concrete failure: a polled status register read once because it was not volatile, a misaligned access that traps or takes extra cycles, a shared library missing at run time. Practise this aloud for the volatile, alignment, malloc versus calloc and linking questions.

Writing the linked-list insert for the middle of the list and missing the edges

Handle position zero (the head changes), a position past the end, an empty list and a failed allocation before you write the main loop, and say which pointer you return or update. Trace the code by hand on a one-node list. The same care applies to your in-place reversal and bit-flip snippets, including shifting an unsigned value.

Answering the intermittent-bug or frozen-test-suite question with a single guess such as a race condition

Give a method before a theory: make the failure reproducible, log inputs and timestamps, split software from firmware from hardware, compare failing and passing runs, and name the experiment that would confirm or reject each hypothesis. For a freeze, say you would take stack dumps and check for a lock or a blocking read without a timeout.

Answering the defect escape rate question with only 'add more testing'

Define a defective unit and an escape (a unit that passed the check but is faulty), then lay out layers: static analysis, unit and integration tests, a validation step that rejects a unit whose firmware or configuration does not match an expected value, and root-cause review of every escape. Say how you would measure the escape rate.

Describing resume projects as team work without your own contribution, or listing tools you cannot discuss in depth

For every project, write your role, the design constraint, one trade-off between memory, speed and schedule, and how you tested it. Remove anything from the resume you could not explain for five minutes, and be honest about which parts a colleague built.

Questions & practice

Implement a function to insert a new node at a specific position in a single linked list.

Medium · Data Structures, Algorithms & C/C++ Fund

Approach

  1. Pin down the contract before coding: 0-based or 1-based position, what to do when position exceeds the length (return an error, or append), and negative input. Use a signature like int insert_at(Node **head, int pos, int value) so inserting at the head can update the caller's pointer.
  2. Head case: new->next = *head; *head = new. Otherwise walk pos-1 steps to the predecessor; if the walk hits NULL first, the position is out of range, so return an error without leaking the node (free it, or allocate only after validating).
  3. Link order matters: new->next = prev->next first, then prev->next = new. Doing it the other way round loses the rest of the list.
  4. A pointer-to-pointer walk removes the head special case: for (; pos > 0 && *pp; pos--) pp = &(*pp)->next; if (pos > 0) return error; new->next = *pp; *pp = new. Inserting at pos == length lands at the tail correctly.
  5. Complexity is O(pos) time and O(1) extra space. Check malloc for NULL, and test empty list, position 0, tail position and one past the tail.

Follow-up

  • Reverse the same singly linked list in place without allocating extra memory.
  • What changes for a doubly linked list, and which pointers must be updated in which order?
  • How would you make this insert safe if two threads can modify the list concurrently?

Describe the volatile keyword in C/C++ and explain when and why you must use it in embedded programming.

Medium · Data Structures, Algorithms & C/C++ Fund

Approach

  1. State the semantics: volatile tells the compiler that every read and write of the object is an observable side effect, so it must perform each access as written, cannot keep the value in a register, and cannot remove or merge accesses or reorder them relative to other volatile accesses.
  2. Give the three classic uses: memory-mapped hardware registers (a polled status register), variables written in an interrupt handler and read in the main loop, and flags set in signal handlers (volatile sig_atomic_t). Locals changed between setjmp and longjmp are a fourth.
  3. Show the bug it prevents: while (!(*(volatile uint32_t *)STATUS & READY)) {} - without volatile the optimiser may hoist the load out of the loop and spin forever on a stale value.
  4. Say what it does not do: it gives no atomicity (count++ on a volatile is still load-modify-store), no CPU memory barriers and no inter-thread synchronisation. For threads use std::atomic, C11 _Atomic or a mutex; for multi-byte data shared with an ISR, briefly disable interrupts around the access.
  5. Know the placement: volatile uint32_t *p makes the pointed-to data volatile; uint32_t * volatile p makes the pointer itself volatile. A read-only status register is typically declared const volatile.

Follow-up

  • Can a variable be both const and volatile? Give a real example.
  • Why is a volatile bool not enough to signal between two threads on a multi-core CPU?
  • An ISR increments a volatile 32-bit counter on an 8-bit MCU; what can the main loop read wrong, and how do you fix it?

How do bitwise operators work? Write a snippet to flip the $n$-th bit of an integer.

Medium · Data Structures, Algorithms & C/C++ Fund

Approach

  1. Name the operators and their uses: & masks bits, | sets bits, ^ toggles bits, ~ inverts all bits, << and >> shift. Then the four idioms: flip x ^= (1u << n); set x |= (1u << n); clear x &= ~(1u << n); test (x >> n) & 1u.
  2. Use unsigned types: in C, 1 << 31 on a 32-bit int overflows a signed value (undefined behaviour), and shifting by a count greater than or equal to the type's width is undefined for any type. For 64-bit values write 1ULL << n or (uint64_t)1 << n, and validate n is in 0..width-1.
  3. Right-shifting a negative signed value is implementation-defined in C, which is another reason to do bit work on uint8_t/uint32_t.
  4. Hardware register angle: x |= mask is a read-modify-write, so an ISR touching the same register in between can lose an update; and on write-1-to-clear status registers, writing back the read value can clear other pending flags. Some MCUs provide separate set/clear registers for this reason.
  5. Useful extras: x & (x - 1) clears the lowest set bit (count set bits in O(number of set bits)), and x != 0 && (x & (x - 1)) == 0 tests for a power of two.

Follow-up

  • Count the set bits in a 32-bit integer; what is the complexity of your method?
  • Extract a 3-bit field at bits 4-6 of a status register and write a new value into it without disturbing the other bits.
  • Why does XOR-swapping two variables fail when both arguments point to the same location?

What is the difference between static and dynamic linking, and how does each impact binary size and execution

Medium · Data Structures, Algorithms & C/C++ Fund

What is the difference between static and dynamic linking, and how does each impact binary size and execution speed?

Approach

  1. Define both: static linking copies the needed object modules from .a archives into the executable at build time; dynamic linking records dependencies (.so on Linux, .dll on Windows) that the loader maps at start-up, resolving symbols through the PLT/GOT, optionally lazily on first call.
  2. Binary size: a static binary is larger on disk but self-contained (only referenced archive members are pulled in, not the whole library). A dynamic binary is smaller, and the shared library's code pages are shared in RAM across processes that use it.
  3. Execution speed: static avoids load-time relocation and symbol lookup, so start-up is faster, and calls are direct. Dynamic calls go through PLT indirection and position-independent code reaches globals through the GOT, a small overhead that is usually negligible next to the deployment trade-offs; measure before claiming more.
  4. Operational trade-off: dynamic lets you patch a library (for example a security fix) without relinking every program, but brings version and ABI mismatches or a missing .so on the target; static simplifies shipping a single image to an embedded or factory system, but every library fix needs a rebuild. Bare-metal firmware is effectively all static.
  5. Inspection tools: ldd and readelf -d (NEEDED entries) show dynamic dependencies, nm lists symbols, and rpath or LD_LIBRARY_PATH control where the loader searches.

Follow-up

  • What happens at run time if a required .so is missing or has an incompatible version?
  • How would you shrink a statically linked binary (for example -ffunction-sections -fdata-sections with --gc-sections, and strip)?
  • When would you load a library explicitly with dlopen instead of linking against it?

If you were given the individual parts of a complex mechanical system (like a bicycle or lawn mower) without a

Medium · Scenario-Based Problem Solving & Logic

If you were given the individual parts of a complex mechanical system (like a bicycle or lawn mower) without any instructions, how would you approach assembling it?

Approach

  1. Start with an inventory: lay out and group every part (structural, moving, fasteners, cables or electronics), count duplicates, and photograph the layout so I can return to it. Matching counts often reveal symmetry (two wheels, two pedals).
  2. Work out the end state and the core: identify the part everything else mounts to (for a bicycle, the frame) and the functions the finished object must perform, then map interfaces by thread size, mating shapes and fastener types to see what can attach where.
  3. Build as subassemblies in dependency order, core outward, which is a topological sort of the attachment graph. Test each subassembly before integrating it (the wheel spins true, the brake engages) so a fault is found where it was introduced.
  4. Use outside evidence and treat surprises as data: wear marks, labels and similar systems I know. A leftover part or a part that fits two places means a wrong assumption, so I back up to the last verified step instead of forcing it.
  5. Close with the software parallel: this is the same method as bringing up an undocumented board or codebase - inventory, find the core, integrate incrementally, verify each step - and finish with a safety and function check before use.

Follow-up

  • You finish and one part is left over. What do you do?
  • Two parts appear to fit the same mount. How do you decide which belongs there?
  • How would you apply the same method to an unlabelled piece of hardware with no documentation?

Walk me through how you isolate a sporadic bug in a production line software system that only fails 1 out of e

Medium · Scenario-Based Problem Solving & Logic

Walk me through how you isolate a sporadic bug in a production line software system that only fails 1 out of every 1,000 runs.

Approach

  1. Quantify first: at a 1-in-1,000 failure rate, the chance of seeing at least one failure in n runs is 1 - 0.999^n, so about 3,000 runs give roughly a 95% chance of reproducing it. Conversely, after a fix, zero failures in about 3,000 runs supports (rule of three) a failure rate below 1 in 1,000 at roughly 95% confidence.
  2. Mine the failures I already have: line each failure up against station, fixture, unit serial, software and firmware version, time since power-up, run count, operator and temperature. A cluster on one fixture points to hardware; failures spread evenly across stations point to software or timing.
  3. List the usual suspects for intermittent faults: race conditions and timing windows, uninitialised memory, timeouts set too close to typical latency, resource exhaustion after N runs (leaked handles, memory, file descriptors), and flaky connectors or contacts.
  4. Raise the failure rate to make it debuggable: loop the test under stress, inject delays or jitter, run under ThreadSanitizer, AddressSanitizer or Valgrind, and log to an in-memory ring buffer dumped on failure, since heavy logging can change timing and hide the bug.
  5. Separate software from hardware by swapping one variable at a time (same unit on another fixture, same fixture with another unit, a known-good software version), and use git bisect if it is a regression. Meanwhile contain it on the line, for example by flagging retested units, without masking the defect.

Follow-up

  • How many clean runs do you need before you can claim the fix worked?
  • Adding logging makes the failure disappear. What does that tell you, and what do you do next?
  • The line cannot stop while you investigate. How do you contain the risk in the meantime?

How do you define a defective product software check, and what strategies would you use to minimize the defect

Medium · Scenario-Based Problem Solving & Logic

How do you define a defective product software check, and what strategies would you use to minimize the defect escape rate toward zero?

Approach

  1. Define the terms: a software check compares a measured or read-back value against a spec limit and decides pass or fail for a unit. An escape is a defective unit that passes (a false pass); the opposite error, a false fail, rejects good units and costs yield. Escape rate is defective units shipped over units shipped, usually estimated from downstream and field catches.
  2. Make every check fail closed: an exception, timeout, missing measurement or unreadable config must fail the unit, never fall through to pass. Log the measured value and limits, not just a boolean, and verify firmware and configuration by version read-back plus checksum or hash rather than by trusting the flash step.
  3. Prove the check works: run golden (known-good) and known-bad units with seeded faults regularly so each test shows it can catch the defect it targets, and check measurement repeatability before tightening limits.
  4. Close the loop: every downstream or field failure gets a root cause and a new or strengthened check; trend measurements with control charts to catch drift before units start failing; and interlock stations so a unit cannot skip a test or ship with an incomplete record.
  5. State the trade-off: tighter limits and more checks lower escapes but raise false fails and test time, so 'toward zero' means layered screening and guardbanded limits, not one perfect test.

Follow-up

  • How do you estimate an escape rate when escapes are by definition the defects you did not catch?
  • What checks would you add so a station can never pass a unit whose firmware configuration is corrupt?
  • A new check catches more defects but doubles false fails. How do you decide whether to keep it?

How do you prioritize software fixes when faced with tight shipping deadlines for a customer's hardware releas

Medium · Scenario-Based Problem Solving & Logic

How do you prioritize software fixes when faced with tight shipping deadlines for a customer's hardware release?

Approach

  1. Triage by impact, likelihood and detectability: first anything that could ship a bad unit, damage hardware or stop the line; then customer-visible functional failures; then issues with a cheap workaround; cosmetic issues last.
  2. For each candidate fix, gather the facts that decide it: how often it occurs, how many units or customers it touches, whether a workaround exists, and the risk of the fix itself (size of the change, what it could regress).
  3. Choose per item between fix now, mitigate (a workaround, a config flag, an extra test screen) and defer as a documented known issue, and get that ranked list agreed with the people who own the release and quality rather than deciding silently.
  4. Protect the release while fixing: minimal-diff changes on the release branch, full regression rerun on the final build, a rollback path, and no unrelated refactoring slipped in.
  5. Communicate early: if a must-fix item cannot make the date, raise it with the evidence as soon as I know, with options (partial ship, workaround, date change), instead of on the last day.

Follow-up

  • A customer escalates a cosmetic issue while you have a rare data-integrity bug open. Which goes first, and how do you explain the choice?
  • The fix for a rare bug is large and risky. Do you ship it?
  • What would you change in the process so fixes do not pile up before the next release?

Describe the key differences between process threads and OS processes in terms of memory allocation and IPC.

Medium · Systems, OS & Hardware Integration

Approach

  1. Memory: each process has its own virtual address space, page tables and file-descriptor table; threads in one process share the address space (code, globals, heap, open files) but each has its own stack, registers and thread-local storage.
  2. Cost: creating a thread is lighter than creating a process; fork() uses copy-on-write so pages are not copied up front, but it still builds new page tables. Switching between threads of the same process avoids an address-space switch.
  3. Communication: threads communicate through shared memory directly, which requires mutexes, condition variables or atomics. Processes need IPC: pipes, Unix domain or TCP sockets, message queues, or shared memory (shm_open plus mmap) paired with a semaphore, plus signals for notification.
  4. Explain a data race: counter++ is load, add, store, so two threads can interleave and lose an update; fix with a mutex or an atomic increment. Processes avoid accidental sharing but pay for copying or explicit setup.
  5. Isolation trade-off: a segfault in one thread kills the whole process, while a separate process contains the crash, which is why a test harness may run a flaky driver in a child process. In CPython the GIL stops threads running Python bytecode in parallel, so CPU-bound work uses multiprocessing.

Follow-up

  • What happens to the other threads when a multithreaded process calls fork()?
  • When would you choose shared memory over a pipe or socket, and what extra work does it need?
  • Show how a data race occurs on a shared counter and two ways to prevent it.

Explain how memory alignment works and why unaligned memory access can impact execution performance.

Medium · Systems, OS & Hardware Integration

Approach

  1. Define alignment: a type with alignment N must sit at an address that is a multiple of N (on common ABIs a 4-byte int is 4-aligned). The compiler pads structs so each member is aligned and the total size is a multiple of the largest member alignment: struct {char a; int b; char c;} is typically 12 bytes, reordered as {int b; char a; char c;} it is 8.
  2. Why unaligned access is slower: the hardware loads aligned words and cache lines, so an unaligned value can span two words, two cache lines or even two pages and needs extra memory operations. x86 handles it in hardware with a possible penalty, while some cores (for example Cortex-M0) raise a fault, and some systems trap and emulate it in the kernel, which is far slower.
  3. Correctness, not just speed: aligned word-sized accesses are usually atomic, unaligned ones may tear. Casting a char buffer to uint32_t * and dereferencing it is undefined behaviour in C (alignment and strict aliasing); memcpy into an aligned variable is the portable way to parse packed data.
  4. Tools: sizeof, offsetof and alignof/_Alignof to check layout; attribute((packed)) or #pragma pack for wire formats, noting that taking the address of a packed member can produce a misaligned pointer; alignas or aligned_alloc for DMA buffers or cache-line alignment.
  5. Tie it to performance work: put hot fields together, order struct members from largest to smallest to cut padding, and align per-thread data to cache lines to avoid false sharing.

Follow-up

  • What is sizeof for a struct of char, double, char on a typical 64-bit platform, and how would you shrink it?
  • How do you safely parse a packed binary header received over a serial link?
  • What is false sharing, and how does alignment fix it?

Explain why the owner filter ignores the listing index

Medium · composite index · query plans · sort elimination

The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.

Approach

  1. Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
  2. For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
  3. Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
  4. Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.

Follow-up

  • 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
  • A dashboard runs this for 40 owners in one page load. What changes about the design?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

Explain the process of interrupt handling in an operating system and what happens during a context switch.

Medium · Systems, OS & Hardware Integration

Approach

  1. Walk the interrupt path: a device raises an IRQ, the interrupt controller (APIC on x86, GIC on ARM application cores, NVIC on Cortex-M) prioritises it, the CPU finishes the current instruction, saves minimal state (program counter, flags; Cortex-M stacks R0-R3, R12, LR, PC and xPSR in hardware), switches to privileged mode and jumps through the vector table or IDT to the handler.
  2. The handler acknowledges the device, does the minimum, and defers the rest: in Linux, top half versus bottom half (softirqs, tasklets, workqueues, threaded IRQs). It signals end of interrupt and returns, restoring the saved state. ISRs must not sleep, block on a mutex or call non-reentrant code such as malloc.
  3. Context switch: when the scheduler picks another thread (timer preemption, a blocking call, a higher-priority thread waking), the kernel saves the current thread's registers, stack pointer and program counter into its control block (task_struct, a TCB in an RTOS) and loads the next one's. If the next thread is in a different process it also switches the address space (CR3 on x86, TTBR on ARM).
  4. Separate the two ideas: an interrupt does not always cause a context switch; after the ISR the kernel may return to the same task, and switches only if rescheduling was requested. On Cortex-M RTOSes the switch is typically done in the lowest-priority PendSV exception for this reason.
  5. Discuss cost: the direct save and restore is small, but the indirect cost of cold caches and TLB misses (unless entries are tagged with ASIDs or PCIDs) is often larger, which is why thread switches within one process are cheaper than process switches.

Follow-up

  • Why can you not take a mutex inside an interrupt handler, and what do you use instead?
  • What adds to interrupt latency, and how would you measure it on a board?
  • How does data shared between an ISR and the main loop need to be protected?

How do networking protocols like TCP and UDP differ in reliability and application suitability?

Medium · Systems, OS & Hardware Integration

Approach

  1. TCP: connection-oriented (three-way handshake), a reliable, ordered byte stream using sequence numbers, acknowledgements and retransmission, with flow control (receive window) and congestion control. It has no message boundaries, so the application must frame messages with a length prefix or delimiter.
  2. UDP: connectionless datagrams with no guarantee of delivery, order or uniqueness, but message boundaries are preserved, the header is 8 bytes against TCP's 20-byte minimum, and it supports broadcast and multicast.
  3. Suitability: TCP for firmware or file transfer, control commands and anything where every byte must arrive; UDP for low-latency telemetry, voice and video, DNS lookups and device discovery broadcasts, where a late packet is worthless.
  4. Note protocols that add reliability on top of UDP: TFTP (often used for network boot) acknowledges each block in lock-step, and QUIC builds reliable streams over UDP. Choosing UDP means writing sequence numbers, acks or tolerance for loss in the application.
  5. Mention the pitfalls: TCP head-of-line blocking, Nagle's algorithm delaying small writes (TCP_NODELAY disables it), and UDP datagrams larger than the path MTU being fragmented, where losing one fragment loses the whole datagram.

Follow-up

  • How do you frame messages over a TCP stream, and what bug appears if you assume one recv() equals one message?
  • What happens to a TCP sender when the receiver stops reading?
  • Design a sensor telemetry link over UDP that detects lost and reordered packets.

What steps are involved in writing software that reads data directly from hardware registers or sensor buses?

Medium · Systems, OS & Hardware Integration

Approach

  1. Start from the datasheet: register map, addresses, widths, reset values, access types (read-only, read-write, write-1-to-clear), bit fields, byte order and timing limits, plus the bus parameters (I2C 7-bit address and speed; SPI clock mode CPOL/CPHA and maximum clock).
  2. Choose the access path: on bare metal, memory-mapped registers through volatile pointers to fixed addresses; in a Linux driver, ioremap with readl/writel; from user space, the i2c-dev or spidev interfaces (or UIO) rather than raw pointers. Bring-up order: enable clocks and pin muxing, configure the bus, then talk to the device.
  3. Verify before trusting: read an ID or WHO_AM_I register to confirm the device answers, then configure it, wait for a data-ready flag (or use an interrupt or DMA instead of polling), and read multi-byte values in one burst so high and low bytes come from the same sample.
  4. Convert and validate: decode two's-complement raw values, apply the scale factor to get engineering units, and range-check the result. Handle errors explicitly: timeouts on every transaction, NACK handling with bounded retries, CRC if the sensor provides one, and I2C bus recovery (clocking SCL until a stuck slave releases SDA).
  5. Test it: put a logic analyser or oscilloscope on the bus to compare real waveforms with the datasheet, and unit-test the conversion and error paths against a mocked hardware abstraction layer.

Follow-up

  • When would you poll, use an interrupt, or use DMA to read the sensor?
  • An I2C sensor suddenly stops acknowledging. What are your steps?
  • Both an ISR and the main loop modify bits in the same control register. What can go wrong, and how do you prevent it?

Walk me through a challenging project listed on your resume, detailing your specific architectural contributio

Medium · Resume Deep-Dives & Behavioral Fit

Walk me through a challenging project listed on your resume, detailing your specific architectural contributions and technical trade-offs.

Approach

  1. Structure the walkthrough: the problem and its constraints with real numbers (data rates, memory budget, deadline), the architecture as a quick sketch, then my own part stated in the first person ('I wrote the driver layer', not 'the team built').
  2. Pick one trade-off and go deep: the options I considered, the constraint that decided it (memory against speed, complexity against the deadline) and what the choice cost later. Have a second trade-off ready.
  3. Explain how I validated it: unit tests, integration tests, simulation or hardware-in-the-loop, and the hardest bug I found, with the debugging sequence that isolated it.
  4. End with a measurable result and an honest lesson: what I would change now and why. Candidates report detailed questioning on every project and tool on the resume, so be able to go two levels deeper on anything named, and drop items you cannot explain.
  5. Rehearse it aloud with a whiteboard-style diagram so each component maps to a sentence, and prepare a short and a long version.

Follow-up

  • Why did you choose that language, architecture or framework over the alternatives?
  • Which design assumption turned out wrong partway through, and how did you refactor?
  • What part of the system would fail first if the load or data volume grew ten times?

Explain the difference between malloc and calloc in C, and describe how memory leaks are detected.

Medium · Data Structures, Algorithms & C/C++ Fund

Approach

  1. Contrast the calls precisely: malloc(size) returns an uninitialised block, while calloc(n, size) returns nsize bytes set to all-zero bits, and a conforming calloc returns NULL if nsize would overflow. Both return NULL on failure, so check the result, and both are released with free().
  2. Mention cost: calloc pays for zeroing, but for large blocks the allocator often gets fresh pages from the OS that are already zero, so the gap can be small. Use calloc when the code relies on zeroed memory, malloc plus explicit initialisation when it does not.
  3. Define a leak as heap memory that becomes unreachable before free() is called: a lost last pointer, an early return that skips cleanup, or p = realloc(p, n) failing and overwriting the only pointer with NULL. A temporary pointer for the realloc result avoids that last case.
  4. Detection on a host: Valgrind memcheck with --leak-check=full, or AddressSanitizer with LeakSanitizer (-fsanitize=address), both of which report the allocation stack of each unfreed block. On a target without those tools, wrap malloc/free to count live allocations and track the heap high-water mark across a long soak run.
  5. Prevention: one clear owner per allocation, a single cleanup path in C (goto cleanup), setting pointers to NULL after free to make double frees visible, and RAII or smart pointers in C++.

Follow-up

  • How would you find a slow leak on a microcontroller that has no Valgrind and only a few KB of heap?
  • What is the difference between a leak, a double free and a use-after-free, and which tool catches each?
  • Why is p = realloc(p, n) a bug, and how do you write it safely?

Suppose an automated hardware test suit freezes mid-execution without raising an exception. What are your step

Medium · Scenario-Based Problem Solving & Logic

Suppose an automated hardware test suit freezes mid-execution without raising an exception. What are your step-by-step troubleshooting actions?

Approach

  1. Classify the hang before killing anything: a frozen process with no exception is either blocked (CPU near 0%: waiting on I/O, a lock or a child process) or spinning (CPU near 100%: an infinite loop or busy-wait). Check CPU, the last log line and its timestamp, and which test step was running.
  2. Capture state from the live process: py-spy dump --pid or faulthandler for Python, gdb attach with thread apply all bt for C/C++, jstack for Java. strace (or the platform equivalent) shows the system call it is stuck in, such as a read() on a serial port.
  3. Check the usual causes: a serial or socket read with no timeout waiting on a device that never answers, two threads holding locks in opposite order, a subprocess blocked writing to a full stdout pipe the parent never reads, or a wait on a hardware ready bit that never sets.
  4. Separate hardware from software: is the unit powered and responding on its console or link, does the hang follow the unit or the fixture when swapped, and does it reproduce against a simulator or loopback?
  5. Fix the cause and harden the framework: timeouts on every I/O call, subprocess output drained with communicate(), a consistent lock order, and a per-test watchdog that kills the step, captures a stack dump and marks the unit failed so a hang can never count as a pass.

Follow-up

  • How would you design a watchdog so a hung test fails the unit instead of blocking the station?
  • The hang only happens on the line, never on your bench. What differs, and how do you capture evidence there?
  • How do you tell a deadlock from a livelock from a stack dump?

Suggested practice checklist

The plan follows the reported rounds and question groups: C and data structures first, then OS and hardware integration, troubleshooting scenarios, project and behavioral stories, and a full mock run. Each day ends with something you can check.

Day 1 — Map the loop and inventory your projects

  • Write the five reported stages in order and mark what you still need to confirm with the recruiter: whether there is a written assessment, and whether the technical stage is one round or two.
  • Pick the two projects on your resume you can discuss best and write for each your role, the design constraint, the main trade-off and how you tested it.
  • Underline every language, tool and protocol on your resume that you could not explain for five minutes, and either prepare it or remove it.

Deliverable: A one-page loop map plus a project sheet for two projects, and a list of resume items to prepare or cut.

Day 2 — C memory and linked lists

  • Explain malloc versus calloc aloud (initialisation, argument form, overflow check on count times size), then list how you would find a leak with Valgrind or AddressSanitizer.
  • Write a singly linked list insert at a given position in C, covering head insertion, out-of-range positions, an empty list and allocation failure, and test it by hand.
  • Write an in-place list reversal using three pointers and trace it on a three-node list.

Deliverable: Two working, hand-traced C functions (positional insert and in-place reversal) and a spoken answer for malloc versus calloc with leak detection.

Day 3 — volatile, bits, alignment and linking

  • Write one example for volatile showing a polled status flag, and explain why it does not make an operation atomic.
  • Write set, clear, toggle and test snippets for bit n, including the unsigned shift detail, and explain how you would flip the n-th bit of an integer.
  • Sketch a struct with padding to show alignment, and list what happens on a misaligned access.
  • Write a short comparison of static and dynamic linking for binary size, load time and updates.

Deliverable: A one-page sheet of four short written answers, each with code or a layout diagram, covering volatile, bit operations, alignment and linking.

Day 4 — Operating system and hardware integration

  • Explain the sequence of an interrupt (save state, jump to the handler, acknowledge, return) and what a context switch saves and restores.
  • Compare threads and processes for address space, cost of creation and IPC, and show how a mutex stops a data race on a shared counter.
  • Write a comparison of TCP and UDP and name one use for each in a test or telemetry setting.
  • Outline the steps to read a hardware register or sensor bus: memory-mapped access, volatile pointers, bus protocol, timeouts and error handling.

Deliverable: Four spoken answers recorded and reviewed, plus a short code sketch for a memory-mapped register read.

Day 5 — Troubleshooting scenarios

  • Write your standard method for an intermittent fault that appears once in a thousand runs, with the first three experiments you would run.
  • Work through the frozen test suite: stack dumps, lock and blocking-read checks, instrument state, and a watchdog or timeout as the permanent fix.
  • Define a defective unit for a software check, then list layers that reduce escapes toward zero and how to track the escape rate.
  • Answer the assemble-without-instructions question out loud using an inventory, grouping, hypothesis and test sequence.

Deliverable: A one-page troubleshooting checklist and four spoken answers with the hypothesis and test named at each step.

Day 6 — Behavioral stories and prioritization

  • Write a situation, decision and result outline for each of the four reported behavioral prompts, using first person and real examples.
  • Prepare an answer for ordering fixes ahead of a customer hardware release: severity, blast radius, workaround, risk of the change and communication.
  • Practise each story aloud and cut any part that is not a decision you made.

Deliverable: Five story outlines (four behavioral prompts plus the prioritization answer), each told in under two minutes of speech.

Day 7 — Full mock run

  • Run a mock of the technical stage: write the linked-list insert and a bit snippet cold, then answer three OS and hardware questions aloud.
  • Run a mock of the project discussion: present one project in five minutes, then take follow-ups on your trade-offs and an intermittent-failure scenario.
  • List your three weakest answers and rewrite them.

Deliverable: A recorded mock of the technical stage and project discussion, plus a rewritten version of your three weakest answers.

Stories to rehearse for Celestica

Candidates report that behavioral questions grow out of your past projects and your work with other teams. The reported prompts cover technical disagreement, ambiguous requirements, balancing refactoring against an urgent production fix and learning a new tool fast. Prepare stories in which you made a decision, say what it cost and what you would change, and keep your own contribution clearly separate from the team's.

  1. Walk me through the most technically challenging software bug you personally identified and fixed in your past work.
  2. Describe a scenario where initial design assumptions turned out to be incorrect halfway through development, and how you refactored the software.

Describe a situation where you had a technical disagreement with a team lead or peer. How did you handle it an

Medium · Resume Deep-Dives & Behavioral Fit

Describe a situation where you had a technical disagreement with a team lead or peer. How did you handle it and reach a resolution?

Approach

  1. Choose a disagreement about a technical decision that you were part of, such as an interface design, a locking strategy or a buffering choice, so the story can be about reasoning and not personalities.
  2. State the two positions fairly in a sentence each, including the strongest point in the other person's favour, then spend most of the answer on how you tested them: a benchmark, a prototype, a failing test case, a datasheet or spec reference.
  3. Show how it ended: the evidence settled it, you escalated sensibly, or you agreed on a time-boxed trial. If you lost, say that you committed to the decision and helped carry it out.
  4. Close with the result and one change in how you now handle disagreement. The story that sinks it is one where the other person is simply wrong and you were proved right.

Follow-up

  • What would you have done if your lead had still disagreed after seeing the evidence?
  • Looking back, was any part of their position right, and what did you change as a result?
  • How did the disagreement affect your working relationship afterwards?

Tell me about a time you had to deliver software under incomplete specifications or ambiguous requirements.

Medium · Resume Deep-Dives & Behavioral Fit

Approach

  1. Pick a project where the requirement was unclear, ideally involving another team such as hardware, test or operations, and say in two sentences what was missing.
  2. Show the first steps: who you asked, which questions you put, and how you wrote your assumptions down so they could be confirmed or corrected.
  3. Describe how you limited the cost of being wrong: building the smallest slice that tested the riskiest assumption, putting the uncertain part behind a clean interface, or keeping a configuration option instead of hard-coding a guess.
  4. Give the outcome, including an assumption that turned out wrong and how cheaply you fixed it. The mistakes to avoid are waiting for a perfect spec and silently building on guesses.

Follow-up

  • How did you decide when you had enough information to start?
  • Which assumption turned out wrong, and what did it cost?
  • How did you keep the other team informed while the spec was still moving?

How do you manage your time when balance needs to be struck between long-term code refactoring and immediate l

Medium · Resume Deep-Dives & Behavioral Fit

How do you manage your time when balance needs to be struck between long-term code refactoring and immediate line-stop bug fixes?

Approach

  1. Start with the principle: a bug that is stopping a production line comes first, because the cost is immediate and shared, but the refactor is not abandoned, it is scheduled.
  2. Walk through a real case: contain the line stop first with the smallest safe fix or workaround, ship it with a regression test, then write down the root cause and the cleanup it points to.
  3. Explain how you protect refactoring time: tie it to the recurring bug it would remove, split it into small steps that can be merged independently, and agree on timing with your lead.
  4. Mention communication: tell the people affected what was fixed now, what was deferred and why. The mistake is presenting it as always urgent first or always refactor first, without a decision rule.

Follow-up

  • How do you decide that a refactor can no longer wait?
  • How would you make the case for refactoring time to a manager under shipping pressure?
  • What do you do when quick fixes keep piling up in the same module?

Describe a situation where you had to quickly learn a brand-new tool or programming framework to solve an urge

Medium · Resume Deep-Dives & Behavioral Fit

Describe a situation where you had to quickly learn a brand-new tool or programming framework to solve an urgent operational issue.

Approach

  1. Choose a story with real urgency, such as a failing test station, a tool you had to use to read logs or a framework behind a stalled task, and name the tool exactly.
  2. Explain how you learned only what the problem needed: reading the key documentation, running a minimal example, asking a colleague with experience, and reproducing the issue in a safe place before touching anything live.
  3. Show how you checked your understanding, for example with a test that failed before the change and passed after it, or a review from someone who knew the tool.
  4. Finish with the result, what you left behind for the team (notes or a script) and what you still did not know. Overclaiming mastery of a tool after a few days is the mistake that sinks it.

Follow-up

  • How did you know your understanding of the tool was correct and not just plausible?
  • What did you get wrong at first, and how did you find out?
  • What did you do afterwards to learn the tool properly?