Skip to main content

Rust engineer technical fundamentals interview questions

13 questions on technical fundamentals for rust engineer candidates. Expect prompts such as “Explain ownership across an API boundary” and “Explain interrupts versus polling in firmware”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 13 of 13 technical fundamentals questions.

As asked

You are designing a Rust library API that accepts user data, transforms it, and returns a result. When would you take ownership, borrow immutably, borrow mutably, or accept Cow?

Sample answer outline

A strong answer starts from the caller contract, not from syntax. Borrow immutably when the function only needs to read data and should not force allocation or transfer of lifetime responsibility. Borrow mutably when the function must update caller-owned state and exclusive access is part of the API contract. Take ownership when the callee must store, move across threads, or consume the value. Cow is useful when most calls can borrow but a minority need normalisation or copying. Candidates often trip by cloning to satisfy the compiler instead of redesigning ownership so the data flow is explicit.

Reference implementation (rust)

Rust
use std::borrow::Cow;

fn normalise_name(input: Cow<'_, str>) -> Cow<'_, str> {
    if input.chars().any(|c| c.is_uppercase()) {
        Cow::Owned(input.to_lowercase())
    } else {
        input
    }
}

Expect these follow-ups

  • How would your choice change if the value is stored in a background worker?
  • When is Clone an acceptable API requirement?
  • How do lifetimes appear in the public signature and what does that communicate?
ownershipapi-designlifetimes

As asked

A sensor produces data at irregular intervals and the device runs on battery. When would you use interrupts instead of polling, and what can go wrong?

Sample answer outline

Interrupts let the CPU sleep and react when the sensor signals data, which is often better for irregular events and power-sensitive devices. Polling can be simpler and more predictable when timing is fixed, the check is cheap, or the hardware interrupt line is unreliable. Keep interrupt service routines short: capture the event, clear the flag, and defer real work to the main loop or RTOS task. Discuss debouncing, missed interrupts, priority inversion, shared data races, and volatile or atomic access. Interviewers want practical embedded judgement, not a blanket rule that interrupts are always better.

Expect these follow-ups

  • How do you safely share data between an ISR and the main loop?
  • When can polling be more deterministic than interrupts?
  • How would you debug an interrupt storm in the field?
firmwareinterruptspower

As asked

What are the three fundamental ownership rules in Rust? State them precisely as the language specification does.

Sample answer outline

Each value has exactly one owner. There can only be one owner at a time. When the owner goes out of scope the value is dropped. Candidate should be able to state these cleanly and then explain why each rule exists in terms of memory safety.

Expect these follow-ups

  • Which rule does the Copy trait relax and how?
  • How does the Drop trait interact with rule three?
ownershipfundamentalsmemory-safety

As asked

What is the difference between the Copy and Clone traits? What constraints must a type satisfy to implement Copy, and why can't String implement Copy?

Sample answer outline

Copy is a marker trait for types whose bits can be duplicated safely with a memcpy; it is implicit at assignment. Clone is explicit and may run arbitrary code. A type can implement Copy only if all its fields are Copy and it does not implement Drop. String cannot be Copy because it owns heap memory and a bitwise copy would create two owners of the same allocation, causing a double-free.

Expect these follow-ups

  • Can you implement Clone without Copy?
  • What does derive(Clone) generate for a struct with a Vec field?
copyclonetraitsfundamentalsownership

As asked

When do you use Option and when do you use Result? Give me an example of a function that should return each, and explain why you would not use Option where Result is more appropriate.

Sample answer outline

Option represents the presence or absence of a value with no error information: vec.get(index), HashMap::get. Result represents operations that can fail with a reason: file reads, network calls, parsing. Using Option where Result is appropriate loses the error context and forces callers to guess why the operation failed, making diagnostics harder. Candidate should note that Option<T> is isomorphic to Result<T, ()> but carries no semantic error information.

Expect these follow-ups

  • How do you convert between Option and Result using ok_or and ok?
  • When is it appropriate to panic instead of returning an error?
resultoptionerror-handlingfundamentals

As asked

Explain Send and Sync. How do they affect a Rust service that shares state between async tasks and worker threads?

Sample answer outline

Send means a value can be moved to another thread, while Sync means references to a value can be shared between threads. Most plain data structures get these auto traits when their fields are also Send or Sync, but Rc, RefCell, raw pointers and FFI handles need care. In async code, a future spawned onto a multi-threaded runtime usually must be Send because it may resume on a different worker. Shared mutable state typically needs Arc plus Mutex, RwLock, atomics, or a message-passing design. The interviewer wants to hear that thread safety is encoded in types, but not magically solved if blocking locks or poor contention choices are used.

Expect these follow-ups

  • Why is Rc not Send or Sync, and why is Arc different?
  • What happens if a non-Send value is held across an await?
  • When would you choose channels over Arc<Mutex<T>>?
concurrencyasyncthread-safety

As asked

Explain the three ways a Rust closure can capture variables from its environment. What traits correspond to each capture mode and when does the compiler choose each?

Sample answer outline

By shared reference (Fn), by mutable reference (FnMut), or by value (FnOnce). The compiler uses the least restrictive capture mode needed. move forces by-value capture regardless. Fn implies FnMut implies FnOnce in terms of what can call what. Candidate should explain why move is needed when spawning a thread with a closure that references stack data, since the stack frame may not outlive the thread.

Expect these follow-ups

  • Can you call an FnOnce closure more than once? Why not?
  • How does a closure that captures by value differ from a regular struct that implements call traits?
closurescapturesfn-traitsfundamentals

As asked

What is the difference between writing T: Debug + Clone inline on a function and using a where clause? When would you prefer one over the other?

Sample answer outline

Functionally identical; where clauses improve readability when bounds are long, complex, or involve associated types like T: Iterator<Item = &'a str>. Where clauses also allow bounds on complex expressions beyond a single type parameter, such as (T, U): SomeTrait. Style guides generally prefer where clauses for non-trivial bounds.

Expect these follow-ups

  • Can you put a bound on a reference type in a where clause?
  • How do you express a bound that only applies when another type parameter satisfies a condition?
genericstraitswhere-clausesfundamentals

As asked

Rust has several string types. Explain the difference between String, &str, OsString, and PathBuf and give a concrete scenario where you would use each.

Sample answer outline

String is an owned, heap-allocated UTF-8 string. &str is a borrowed slice of UTF-8 data (into a String, a string literal, or any contiguous UTF-8 buffer). OsString/OsStr are OS-native strings that may not be UTF-8, used when interacting with the OS, environment variables, or paths that may contain non-UTF-8 sequences. PathBuf/Path are wrappers over OsString optimized for file system path manipulation with cross-platform join and extension methods.

Expect these follow-ups

  • Why can converting OsStr to str fail?
  • What is Cow<str> and when is it more efficient than always allocating a String?
stringstypesfundamentalsos

As asked

Explain what it means for Rust iterators to be lazy. What is the difference between an iterator adapter and a consuming adapter, and why does a chain of map().filter() not do any work until you call collect() or for?

Sample answer outline

Lazy means no computation happens until an element is demanded. Adapter methods like map, filter, and take return new iterator types that record the operation; consuming adapters like collect, for_each, sum, or a for loop drive the pull chain. This enables the compiler to fuse operations and avoid intermediate allocations. Candidate should trace a simple filter().map().collect() call through the generated code mentally.

Expect these follow-ups

  • What is the difference between Iterator::collect and Iterator::cloned?
  • When would you reach for a collecting iterator over a streaming one?
iteratorslazy-evaluationfundamentalsperformance

As asked

What is interior mutability in Rust and why is it sometimes necessary? Compare RefCell, Cell, and Mutex in terms of what they allow and what guarantees they trade away.

Sample answer outline

Interior mutability allows mutation through a shared reference by deferring the exclusivity check to runtime rather than compile time. Cell works for types that are Copy (or small enough to replace atomically) and provides get and set without runtime borrow tracking; it never hands out a reference to the inner value. RefCell works for any type and provides borrow and borrow_mut that track borrows at runtime, panicking if you violate the exclusive-or-shared rule. Mutex is the thread-safe equivalent, blocking the calling thread rather than panicking. UnsafeCell is the primitive that underpins all three. Use Cell for simple copyable values in a single thread, RefCell for shared mutation in a single thread, Mutex for multi-threaded shared mutation.

Expect these follow-ups

  • When would you use RwLock instead of Mutex?
  • What is the UnsafeCell type and how does it underpin all interior mutability?
interior-mutabilityrefcellcellmutexfundamentals

As asked

Explain Box, Rc, and Arc. When do you reach for each, and what is the cost of going from Box to Arc for every allocation?

Sample answer outline

Box is single-owner heap allocation with no overhead beyond the pointer. Rc adds a reference count for shared ownership in a single thread; cloning increments the count, drop decrements it, and the value is freed at zero. Arc uses atomic operations on the count making it thread-safe at the cost of a cache-coherency roundtrip on each clone/drop. Going from Box to Arc everywhere adds that atomic overhead on every shared ownership event, which matters in high-throughput code.

Expect these follow-ups

  • Why can't you use Rc across thread boundaries?
  • How do you avoid Arc clone overhead for read-heavy data with many readers?
boxrcarcsmart-pointersfundamentals

As asked

Explain how Rust's module system works. What is the difference between pub, pub(crate), and pub(super), and how does the file system map to module hierarchy?

Sample answer outline

Each file is a module; modules can be inline or map to files in a directory with mod.rs or the modern foo.rs-plus-foo/ layout. pub makes items visible to any code. pub(crate) limits to within the current crate. pub(super) exposes to the parent module only. The default is private to the defining module. Candidate should explain that this lets you build a well-encapsulated API where internal helpers are not accessible to downstream users.

Expect these follow-ups

  • How do you re-export items to flatten a module hierarchy for consumers?
  • What does pub(in path) let you do that pub(super) does not?
modulesvisibilitycargofundamentals