RecallDeck
Interview track

Rust Developer interview prep

A spaced-repetition deck of 136+ Rust Developer interview questions — organised by topic and difficulty, and resurfaced right before you'd forget. Preview a few cards below, then choose access to study the whole track on an Anki-style SM-2 schedule.

136 cards10 topics
See plans and start trial

7 days free on monthly or yearly · every feature included.

What's covered

Every topic in this track, grouped the way you'd study it.

Ownership, Borrowing & Lifetimes

12 cards
Ownership & Lifetimes

Traits, Generics & Dispatch

11 cards
Traits & Dispatch

Smart Pointers & Interior Mutability

7 cards
Smart Pointers

Errors, Collections & Ecosystem

10 cards
Errors & Ecosystem

Concurrency: Send/Sync & Atomics

8 cards
Send/Sync & Atomics

Async/await & Tokio

10 cards
Async & Tokio

Unsafe, Memory & FFI

7 cards
Unsafe & Memory

Live Coding & Applied Tasks

7 cards
Live Coding

System Design

29 cards
System Design

Behavioral

35 cards
Behavioral

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?

Short answer: Rust checks ownership and borrows statically, at compile time. The borrow checker proves every reference lives no longer than its data and that there's never simultaneous aliasing plus mutation — so use-after-free, double-free and data races are ruled out before the program ever runs. No GC and no reference counting by default: you pay in compile time and learning curve.

In depth:

  1. Ownership — every value has exactly one owner; when it leaves scope the memory is freed deterministically (Drop), with no runtime pauses.
  2. Borrowing — the compiler enforces "either many &T or one &mut T", which is what kills data races statically.
  3. Lifetimes — the borrow checker matches the lifetimes of references and data, preventing a dangling reference from ever existing.
  4. Zero-cost abstractions — the compiler pays for safety; there are no checks in the binary.
Criterion Rust GC languages
When checked compile time runtime
Runtime cost zero GC pauses / refcount
Failure mode compile error leaks, latency, rare bugs

⚠️ Common mistake: answering "Rust counts references, like ARC in Swift" or "there's a GC under the hood". By default there's neither — Rc/Arc are an opt-in tool, not the memory-safety mechanism.

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:

  1. Polymorphism — a shared interface via trait + default methods; statically (generics) or dynamically (dyn).
  2. Blanket implimpl<T: Display> ToString for T gives ToString to every Display type at once — reuse without a hierarchy.
  3. Supertrait ≠ inheritancetrait B: A requires implementing A too, but doesn't inherit its state; it's a constraint, not "is-a."
  4. 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?

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:

  1. 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.
  2. Rc<T> — several owners in one thread. clone() increments the strong count, drop decrements it; data is freed when it hits zero. Hands out only &T — put a RefCell inside for mutation.
  3. Arc<T> — like Rc, but the count lives in atomics (fetch_add/fetch_sub). Implements Send/Sync, fit for multithreading; wrap in Mutex/RwLock to 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>?

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:

  1. Option — models "the value may not be there": HashMap::get missed the key, Vec::first on an empty vector. None carries no reason.
  2. Result — models "the operation failed": File::open didn't find the file, str::parse couldn't parse. Err(e) explains what went wrong.
  3. Conversion — they cross over easily: option.ok_or(err) turns None into Err, and result.ok() drops the error and yields an Option.
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?

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:

  1. Structural derivation — the auto trait propagates through fields: a struct of Send fields is itself Send. Raw pointers (*const T, *mut T) deliberately opt out of both, which strips the traits from types built on them.
  2. unsafe impl — if you uphold thread safety manually (e.g. wrapping a raw pointer in correct synchronization), you may write unsafe impl Send for MyType {}. That's an assertion you're responsible for: the compiler can't check it.
  3. Opting out explicitly — a negative impl (impl !Send for T) is nightly-only; that's how Rc is marked inside std. On stable you strip the trait with a marker field like PhantomData<*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?

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:

  1. async fn returns a value — a type impl Future<Output = T>. The call is a state-machine constructor, not the start of the computation.
  2. Laziness — unlike a thread, a future does not spin in the background on its own. Until something polls it, it's inert.
  3. Who drives the future — an .await inside another async function, or tokio::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?

Start your first session in under a minute. Your future self, mid-interview, will thank you.

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 right before you'd forget it — so the answers are still there under pressure on interview day.

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. Actively recalling an answer and grading yourself honestly builds far more durable memory than re-reading notes. RecallDeck schedules each Rust Developer card to reappear at the moment you're about to forget it, so your daily reviews shrink while your recall holds.

Can I try the Rust Developer track before paying?

Yes. Monthly and yearly access include a seven-day trial of the complete Rust Developer track, the full SM-2 scheduler, statistics, flexible pacing, and cram mode. You can cancel online before the first charge.

Other interview tracks

RecallDeckSpaced-repetition interview prep