gitGood.dev
Back to Blog

Top 50 Rust Interview Questions in 2026 (With Answers)

P
Patrick Wilson
12 min read

Top 50 Rust Interview Questions in 2026 (With Answers)

Rust crossed a threshold in 2025 - it's now the second most-used language at AWS for new infrastructure work, the primary language for Cloudflare's edge platform, and the default for new tooling at every company that has burned itself on a Go or Python service that couldn't hit its latency SLOs.

If you're interviewing for systems, infrastructure, embedded, blockchain, or performance-critical backend roles in 2026, expect Rust on the bar. These are the 50 questions that actually came up in interviews this year.


Ownership and Borrowing (1-10)

1. Explain ownership in one paragraph.

Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped. Assignment moves ownership unless the type is Copy. This eliminates the entire class of use-after-free, double-free, and many concurrency bugs at compile time without a garbage collector.

2. What's the difference between &T, &mut T, and T?

  • T - owned value, you can do anything with it
  • &T - shared reference, can read but not mutate (multiple allowed)
  • &mut T - exclusive reference, can mutate (only one at a time)

You can have many &T OR one &mut T, never both simultaneously. This rule is the foundation of Rust's safety.

3. What does Copy vs Clone mean?

Copy types are duplicated implicitly on assignment (integers, bools, fixed-size arrays of Copy types). Clone is explicit - you call .clone(). Use Copy only for cheap, stack-only types. Everything else: derive Clone if you need it.

4. Explain lifetimes.

Lifetimes are compile-time annotations describing how long a reference is valid. The borrow checker verifies that no reference outlives its referent. Most lifetimes are elided - you only write them explicitly when the compiler can't infer.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

5. What is 'static?

A lifetime that lasts the entire program. String literals are &'static str. Required for values that must outlive any caller (e.g., values sent to a thread pool that may run forever).

6. What's a dangling reference and how does Rust prevent them?

A reference to memory that's been freed. Rust's borrow checker rejects code that would create one - the most common pattern is returning a reference to a local variable, which fails to compile.

7. Explain interior mutability.

Sometimes you need to mutate through a shared reference. Use Cell<T> (Copy types), RefCell<T> (runtime borrow checking, single-threaded), Mutex<T> or RwLock<T> (multi-threaded). The cost is moving safety checks from compile time to runtime.

8. When would you use Rc<T> vs Arc<T>?

Rc<T> - reference-counted shared ownership, single-threaded only (cheaper).
Arc<T> - atomic reference counting, safe across threads (slightly more expensive).

Use Arc<Mutex<T>> or Arc<RwLock<T>> for shared mutable state across threads.

9. What's a Box<T> for?

Heap allocation with single ownership. Used for: large values you don't want on the stack, recursive types (Box<Node>), trait objects (Box<dyn Trait>), and FFI.

10. Explain move closures.

By default, closures borrow captured variables. move forces the closure to take ownership. Required when sending closures to another thread or returning them from functions.


Traits and Generics (11-20)

11. What's a trait?

Rust's version of interfaces. Traits define a set of methods a type must implement. Unlike Java interfaces, traits can have default method implementations and you can implement traits for types you don't own (subject to coherence rules).

12. Static dispatch vs dynamic dispatch in Rust?

Generic functions (fn foo<T: Trait>) use static dispatch - the compiler generates a specialized version per type. Trait objects (Box<dyn Trait>, &dyn Trait) use dynamic dispatch via a vtable. Static is faster; dynamic is more flexible.

13. What's the orphan rule?

You can implement a trait for a type only if you own the trait OR the type. Prevents two crates from defining conflicting implementations.

14. Explain associated types vs generic parameters in traits.

Generic parameters (trait Foo<T>) allow multiple implementations per type. Associated types (type Item;) allow only one. Iterator uses associated types because each iterator has exactly one item type.

15. What's the ?Sized bound?

By default, generic type parameters are assumed to be Sized (have a known size at compile time). ?Sized opts out, allowing types like str or [T] that have dynamic size. Required when accepting trait objects or slices generically.

16. Explain From and Into.

If you implement From<T> for U, you get Into<U> for T for free. Convention: implement From, use Into in bounds. Used everywhere for ergonomic type conversions.

17. What does impl Trait mean in return position vs argument position?

Argument position (fn foo(x: impl Trait)) - syntactic sugar for a generic.
Return position (fn foo() -> impl Trait) - returns "some concrete type implementing Trait" without naming it. Useful for closures and complex iterator chains.

18. What are associated functions?

Functions inside an impl block that don't take self. Called with Type::function(). The most common one is Type::new().

19. What's the Drop trait for?

Custom destructor logic. Called automatically when a value goes out of scope. Use for releasing resources (file handles, network connections, locks). Cannot be called manually - use std::mem::drop() to drop early.

20. What's the Send and Sync distinction?

  • Send - safe to transfer ownership across thread boundaries
  • Sync - safe to share &T across thread boundaries

Most types are both. Rc<T> is neither. Cell<T> is Send but not Sync. The compiler enforces these automatically.


Error Handling and Pattern Matching (21-28)

21. Why does Rust avoid exceptions?

Exceptions hide control flow and make resource management harder. Rust uses Result<T, E> and Option<T> to make failure explicit in types. The ? operator makes propagation ergonomic.

22. Explain the ? operator.

Inside a function returning Result or Option, expr? unwraps the success case or returns early with the error. Equivalent to a small match block. The error is automatically converted via From.

23. When do you use Option vs Result?

Option<T> - the value may be absent, but absence isn't an error (e.g., looking up a key in a map).
Result<T, E> - the operation may fail, and you want to know why.

24. What's the difference between unwrap, expect, and ??

  • unwrap() - panic on None/Err. Use only in tests or when failure is impossible.
  • expect("msg") - same, but with a custom panic message. Slightly better.
  • ? - propagate the error to the caller. Always preferable in real code.

25. How do you handle multiple error types in one function?

Either define an enum that wraps each error type and implement From for each, OR use Box<dyn Error> (or libraries like anyhow). Use enums for libraries, anyhow for applications.

26. What's pattern matching in Rust?

match checks a value against patterns and destructures in one step. Exhaustive - the compiler ensures every case is handled. Used heavily because enums are first-class.

match user {
    User::Admin { name } => greet_admin(name),
    User::Guest => greet_guest(),
    User::Banned { reason } => deny(reason),
}

27. Explain if let and while let.

Shorthand for matching one variant. if let Some(x) = opt { use(x) } is cleaner than a full match when you only care about one case. while let Some(x) = iter.next() is the idiomatic loop over an iterator.

28. What's the let else pattern (Rust 1.65+)?

Pattern-match or diverge in one expression. Cleaner than nested ifs.

let Some(user) = lookup(id) else {
    return Err("not found");
};
// `user` is in scope here

Async and Concurrency (29-38)

29. How does async/await work in Rust?

async fn returns a Future. Futures are lazy - they don't run until polled by an executor (Tokio, async-std, smol). The compiler generates a state machine for each async function.

30. Why doesn't Rust have a built-in async runtime?

Pluggability. Different domains need different schedulers - low-latency, work-stealing, single-threaded, embedded. The standard library provides the trait (Future), runtimes provide the polling.

31. What's tokio::spawn?

Schedules a future to run on the Tokio runtime. Returns a JoinHandle that can be awaited. The future must be Send + 'static.

32. Explain select!.

Wait on multiple futures, take the first to complete. Useful for timeouts, racing requests, or handling multiple event sources.

tokio::select! {
    result = api_call() => handle(result),
    _ = tokio::time::sleep(Duration::from_secs(5)) => timeout(),
}

33. What's a "fearless concurrency" example?

Sending data across threads with Arc<Mutex<T>> - the compiler enforces that no thread can access the data without holding the lock, and that ownership is properly shared. The same code in C++ would compile but possibly race.

34. What's Pin and why does it exist?

Some futures contain self-references that would be invalidated by moving them. Pin<P> guarantees the pointee won't be moved. Mostly invisible in normal code - you'll see it when implementing custom futures by hand.

35. When should you use threads vs async?

Threads - CPU-bound work, parallelism, blocking syscalls.
Async - I/O-bound work, many concurrent tasks (10K+).

You can mix: tokio::task::spawn_blocking runs blocking code on a dedicated thread pool from async context.

36. What's "function coloring" and how does Rust handle it?

The complaint that async functions can only be called from async contexts (and vice versa). Rust has the same constraint. Mitigated by block_on (async to sync) and spawn_blocking (sync from async).

37. Explain channels in Rust async (Tokio).

mpsc - multi-producer, single-consumer.
oneshot - send a single value once.
broadcast - multiple consumers see every message.
watch - publish-subscribe, consumers see only the latest value.

Pick by communication pattern.

38. How do you cancel a Rust future?

Drop it. Futures are cancellable by construction - dropping a future stops further polling. Some operations (HTTP requests, file writes) may have already started; their effects aren't undone by cancellation.


Memory and Performance (39-44)

39. How does Rust achieve C-like performance without GC?

Static memory management via ownership. No runtime, no GC pauses, predictable allocation. The compiler optimizes aggressively because aliasing rules give it more freedom than C.

40. What's a zero-cost abstraction?

A high-level construct that compiles to the same machine code you'd write by hand. Iterators, closures, generics, async/await - all designed to be zero-cost.

41. When would you reach for unsafe?

  • FFI (calling C code)
  • Implementing fundamental data structures (Vec, RefCell)
  • Performance-critical hot paths where the borrow checker is too conservative

Wrap unsafe in safe abstractions. Document invariants.

42. What does #[repr(C)] do?

Tells the compiler to lay out the struct using C's rules (predictable field order, no reordering). Required for FFI.

43. Explain stack vs heap allocation in Rust.

Stack - fast, automatic, sized at compile time. Function locals, fixed arrays.
Heap - via Box, Vec, String, etc. Required for dynamic size or shared ownership.

The compiler aggressively optimizes away unnecessary heap allocations.

44. How do you profile a Rust program?

cargo flamegraph for CPU sampling. valgrind --tool=massif or dhat-rs for heap. perf on Linux for system-wide. Tokio's console for async runtime introspection.


Ecosystem and Design (45-50)

45. What's the most important crate to know?

After tokio and serde, probably anyhow (application errors), thiserror (library errors), clap (CLIs), tracing (structured logging), axum or actix-web (HTTP servers). Knowing these signals you've shipped real Rust.

46. How do you organize a Rust project?

cargo new for binaries, cargo new --lib for libraries. Workspaces (Cargo.toml with [workspace]) for multi-crate projects. Convention: keep main.rs thin, put logic in a library crate, share types across binaries.

47. How do you write tests in Rust?

#[cfg(test)] mod tests { ... } for unit tests. tests/ directory for integration tests. cargo test runs both. Use cargo nextest for faster execution and better output.

48. Design a thread-safe LRU cache.

Arc<Mutex<LruCache<K, V>>> is the simplest version. For higher contention, shard the keyspace into N submaps each with their own mutex. For lock-free reads, look at dashmap or moka.

49. When would you NOT use Rust?

  • Throwaway scripts where dev time matters more than runtime
  • Heavy GUI work (the ecosystem is improving but immature)
  • Domains dominated by Python ML libraries (use Python and call Rust for hot loops)
  • Teams without time for the learning curve - Rust pays back, but the ramp is real

50. What's the future of Rust?

Async traits stabilized. Generic associated types matured. Const generics are usable for real work. Linux kernel adoption is growing. The standard library is slowly absorbing patterns from the ecosystem. The language is getting easier to write while keeping its safety guarantees.

The ceiling for Rust skills compounds because the language rewards depth - and senior Rust engineers are still genuinely hard to find.


Final Thoughts

A Rust interview tests two things: do you understand ownership at a deep level, and can you reason about systems? The first is unique to Rust. The second carries over to any systems language.

The fastest way to ace a Rust interview is to ship something nontrivial in Rust before the interview. Read the code, debug it, refactor it. The questions become obvious when you've felt them.


Looking for structured Rust interview prep? gitGood.dev has a Rust question bank covering ownership, async, and more, plus chat-based AI mock interviews and system design walkthroughs.