Rust Developer interview prep
A spaced-repetition deck of 136+ Rust Developer interview questions — organised by topic and difficulty, and scheduled for review based on your ratings. Preview a few cards below, then choose access to study the whole track on an Anki-style SM-2 schedule.
Eligible new monthly or yearly subscribers get 7 days free. Checkout confirms eligibility and charges.
Before you start
Learn basic Rust syntax before starting ownership and borrowing. Rust modules are followed by shared system-design and behavioral questions; practise code examples in a small Rust project.
First pass: Ownership, Borrowing & Lifetimes → Traits, Generics & Dispatch. Begin with the foundational questions in these modules, then follow the outline. Revisit advanced and senior questions as the job requires; all cards remain available from the start.
Shared topics can appear in several tracks and keep one review history. The outline labels shared foundations and role-specific modules; choose your depth for the job you are preparing for.
What's covered
Every topic in this track, grouped the way you'd study it.
Ownership, Borrowing & Lifetimes
12 cardsRole-specific module
Traits, Generics & Dispatch
11 cardsRole-specific module
Smart Pointers & Interior Mutability
7 cardsRole-specific module
Errors, Collections & Ecosystem
10 cardsRole-specific module
Concurrency: Send/Sync & Atomics
8 cardsRole-specific module
Async/await & Tokio
10 cardsRole-specific module
Unsafe, Memory & FFI
7 cardsRole-specific module
Live Coding & Applied Tasks
7 cardsRole-specific module
System Design
29 cardsShared foundations
Behavioral
35 cardsShared foundations
Sample questions
A few cards from the deck — reveal each answer, then choose access to study the full set on a schedule.
How does Rust guarantee memory safety without a garbage collector?
How does Rust guarantee memory safety without a garbage collector?
Short answer: Safe Rust combines ownership, borrowing and type checking to prevent memory errors without requiring a tracing garbage collector. Most reference rules are checked at compile time; some safety mechanisms still run at runtime.
In depth:
- Ownership controls when values are dropped.
Dropcan execute arbitrary cleanup; deterministic destruction does not mean cleanup is free or cannot pause execution. - Shared references ordinarily permit reading; exclusive references permit mutation. Interior-mutability types provide controlled exceptions while preserving safety.
- Lifetimes constrain references so they cannot outlive their referents.
- Bounds checks can run at runtime;
RefCellchecks borrow rules dynamically, andRc/Arcmaintain reference counts. These costs depend on the operations and types chosen. unsafe/FFI code must uphold safety invariants manually. Safe callers rely on those implementations being sound. Rust does not guarantee freedom from leaks, deadlocks or all logic errors.
Watch out: “No mandatory tracing GC” is accurate. “All safety is free and there are no runtime checks” is not.
Sources: Rust interior mutability, unsafe Rust.
Rust has no class inheritance — how do traits and composition replace classic OOP?
Rust has no class inheritance — how do traits and composition replace classic OOP?
Short answer: Traits give interface-style polymorphism — with default methods and blanket impls. A supertrait is an obligation to "also implement that trait," not inheritance of fields and behavior. Code reuse in Rust is built on composition: you embed a type in a struct and delegate (including via Deref).
In depth:
- Polymorphism — a shared interface via
trait+ default methods; statically (generics) or dynamically (dyn). - Blanket impl —
impl<T: Display> ToString for TgivesToStringto everyDisplaytype at once — reuse without a hierarchy. - Supertrait ≠ inheritance —
trait B: Arequires implementingAtoo, but doesn't inherit its state; it's a constraint, not "is-a." - Composition — component fields + method delegation replace class chains; "has-a" instead of "is-a."
| OOP concept | Rust counterpart |
|---|---|
| interface | trait |
| abstract method | trait method with no body |
| default method | default trait method |
| behavior inheritance | composition + delegation |
| interface inheritance | supertrait (trait B: A) |
⚠️ Common mistake: calling a supertrait "inheritance." It drags along no fields or implementation — it's just a requirement to implement another trait.
When do you reach for Box, Rc, or Arc? How do they differ?
When do you reach for Box, Rc, or Arc? How do they differ?
Short answer: Box<T> is a single owner — a plain heap allocation with zero-cost dereference. Rc<T> is shared ownership via a non-atomic reference count, single-threaded only. Arc<T> is the same shared ownership, but the count is updated atomically, so it can be shared across threads (at the cost of atomic operations).
In depth:
Box<T>— one owner, no counter. Use it when a value must live on the heap: recursive structures, trait objects (Box<dyn Trait>), large values. Dereference is free.Rc<T>— several owners in one thread.clone()increments the strong count,dropdecrements it; data is freed when it hits zero. Hands out only&T— put aRefCellinside for mutation.Arc<T>— likeRc, but the count lives in atomics (fetch_add/fetch_sub). ImplementsSend/Sync, fit for multithreading; wrap inMutex/RwLockto mutate.
| Owners | Threads | Type |
|---|---|---|
| one | — | Box<T> |
| many | one | Rc<T> |
| many | several | Arc<T> |
⚠️ Common mistake: reaching for Arc everywhere "just in case". If there's a single owner, Box or even the stack is enough; Arc's atomic operations aren't free, and in one thread they're pure overhead.
When do you use Option vs Result<T, E>?
When do you use Option
Short answer: Option<T> — when a value may simply be absent and the reason doesn't matter (Some/None). Result<T, E> — when an operation can fail and the caller needs the cause (Ok/Err).
In depth:
- Option — models "the value may not be there":
HashMap::getmissed the key,Vec::firston an empty vector.Nonecarries no reason. - Result — models "the operation failed":
File::opendidn't find the file,str::parsecouldn't parse.Err(e)explains what went wrong. - Conversion — they cross over easily:
option.ok_or(err)turnsNoneintoErr, andresult.ok()drops the error and yields anOption.
| Type | Meaning | Typical API |
|---|---|---|
| Option |
value may be absent | HashMap::get, Vec::first |
| Result<T, E> | operation may fail | File::open, str::parse |
⚠️ Common mistake: returning Result where absence is normal (or Option where the caller needs the cause). "Key not found" is an Option, not an error.
What are Send and Sync, and how does the compiler derive them automatically?
What are Send and Sync, and how does the compiler derive them automatically?
Short answer: Send means ownership of a value can be safely moved to another thread; Sync means &T can be safely shared across threads (by definition T: Sync ⇔ &T: Send). These are auto traits: the compiler implements them structurally — a type is Send/Sync if all its fields are. You write nothing by hand.
In depth:
- Structural derivation — the auto trait propagates through fields: a struct of
Sendfields is itselfSend. Raw pointers (*const T,*mut T) deliberately opt out of both, which strips the traits from types built on them. unsafe impl— if you uphold thread safety manually (e.g. wrapping a raw pointer in correct synchronization), you may writeunsafe impl Send for MyType {}. That's an assertion you're responsible for: the compiler can't check it.- Opting out explicitly — a negative impl (
impl !Send for T) is nightly-only; that's howRcis marked insidestd. On stable you strip the trait with a marker field likePhantomData<*const ()>(!Send + !Sync).
| Type | Send | Sync | Why |
|---|---|---|---|
Rc<T> |
no | no | non-atomic reference count |
Arc<T> |
yes | yes | atomic count (when T: Send + Sync) |
Cell<T> / RefCell<T> |
yes | no | unsynchronized interior mutability |
Mutex<T> |
yes | yes | synchronization inside (when T: Send) |
*mut T |
no | no | a raw pointer offers no guarantees |
⚠️ Common mistake: thinking Send and Sync are about a type's "thread-safety in general". Send is about moving ownership between threads, Sync about shared access via a reference; these are distinct guarantees, and a type can have one without the other.
What is a Future, and why does calling an async fn without .await run nothing?
What is a Future, and why does calling an async fn without .await run nothing?
Short answer: A Future is a lazy state machine implementing the Future trait with a poll method. Calling an async fn doesn't start work — it only constructs the future value. The code inside runs only when an executor polls it, i.e. when you .await it or hand it to spawn.
In depth:
async fnreturns a value — a typeimpl Future<Output = T>. The call is a state-machine constructor, not the start of the computation.- Laziness — unlike a thread, a future does not spin in the background on its own. Until something polls it, it's inert.
- Who drives the future — an
.awaitinside another async function, ortokio::spawn, which hands the task to the runtime. Without that, the work never begins.
async fn work() { println!("running"); }
let fut = work(); // nothing happened — no print
fut.await; // only now the body actually runs
⚠️ Common mistake: expecting an async fn to start "like a thread" the moment you call it. A forgotten .await is work that never ran — and often an unused_must_use warning: "futures do nothing unless you .await or poll them."
Ready to make it stick?
Explore a sample, choose a track, and build a review routine that fits your preparation.
Questions about this track
How should I prepare for a Rust Developer interview?
Study the concepts you'll be asked to explain, not just the ones you can code. RecallDeck's Rust Developer track gives you 136+ curated interview questions and resurfaces each one with an Anki-style SM-2 schedule based on your ratings — to practise recalling and explaining them. Combine reviews with coding and mock interviews.
What topics does the Rust Developer track cover?
The Rust Developer track is organised into the core areas Rust Developer interviews actually test, grouped by topic and by difficulty (Concept, Junior, Middle, Senior). You can preview the full outline and sample questions above before signing in.
Is spaced repetition effective for Rust Developer interview prep?
Yes. Active recall and spaced practice can help retain what you study. Grade yourself honestly and check your understanding with practical work. RecallDeck schedules each Rust Developer card using your ratings, so familiar cards return less often and difficult ones get more practice.
Can I try the Rust Developer track before paying?
Eligible new monthly or yearly subscribers can try the complete Rust Developer track and every feature for seven days. Checkout confirms trial eligibility and the amount due; returning subscribers may be charged immediately. Cancel online before the trial ends to avoid its first charge. Lifetime access has no trial.