Interview track

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.

136 cards10 topics
See access options

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 cards

Role-specific module

Ownership & Lifetimes

Traits, Generics & Dispatch

11 cards

Role-specific module

Traits & Dispatch

Smart Pointers & Interior Mutability

7 cards

Role-specific module

Smart Pointers

Errors, Collections & Ecosystem

10 cards

Role-specific module

Errors & Ecosystem

Concurrency: Send/Sync & Atomics

8 cards

Role-specific module

Send/Sync & Atomics

Async/await & Tokio

10 cards

Role-specific module

Async & Tokio

Unsafe, Memory & FFI

7 cards

Role-specific module

Unsafe & Memory

Live Coding & Applied Tasks

7 cards

Role-specific module

Live Coding

System Design

29 cards

Shared foundations

System Design

Behavioral

35 cards

Shared foundations

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: 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:

  1. Ownership controls when values are dropped. Drop can execute arbitrary cleanup; deterministic destruction does not mean cleanup is free or cannot pause execution.
  2. Shared references ordinarily permit reading; exclusive references permit mutation. Interior-mutability types provide controlled exceptions while preserving safety.
  3. Lifetimes constrain references so they cannot outlive their referents.
  4. Bounds checks can run at runtime; RefCell checks borrow rules dynamically, and Rc/Arc maintain reference counts. These costs depend on the operations and types chosen.
  5. 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?

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 impl — impl<T: Display> ToString for T gives ToString to every Display type at once — reuse without a hierarchy.
  3. Supertrait ≠ inheritance — trait 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?

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.

Other interview tracks