yoklainterview sim

Backend Rs Concurrency Send Sync Interview Questions

75 verified Backend Rs Concurrency Send Sync interview questions — solve with answers, learn from explanations, test yourself in a real simulation.

Try the real simulation →

Sample questions

Rs Concurrency Send SyncDifficulty 1
fn main() {
    let name = String::from("worker");
    let h = std::thread::spawn(|| println!("{}", name));
    h.join().unwrap();
}

What happens when this is compiled?
  • aIt compiles and prints worker once the spawned thread gets scheduled
  • bIt compiles, but may print an empty string, since name can be dropped before the thread runs
  • cE0373: the closure may outlive main yet borrows name, and move is required
  • dE0382: name is moved into the closure and then used again by join
Explanation:thread::spawn requires a 'static closure: the new thread might run after main's stack frame is gone, so the closure cannot hold a borrow of a local. The compiler reports E0373 and suggests move ||, which transfers ownership of name into the closure. There is no runtime path here at all; the program never builds.
Rs Concurrency Send SyncDifficulty 1
use std::rc::Rc;
fn main() {
    let r = Rc::new(5);
    let h = std::thread::spawn(move || println!("{}", r));
    h.join().unwrap();
}

What is the result?
  • aIt compiles and prints 5; move hands the Rc to the thread
  • bE0277: Rc<i32> cannot be sent between threads safely
  • cIt compiles, then panics at runtime when the non-atomic reference count is touched from two threads
  • dE0382: r is used after being moved into the closure
Explanation:Rc uses a non-atomic reference count, so the standard library marks it !Send. thread::spawn demands F: Send, and the closure captures r by value, so the whole closure is !Send and the compiler stops with E0277. The message literally says the type "cannot be sent between threads safely". Arc is the thread-safe counterpart and would compile.
Rs Concurrency Send SyncDifficulty 3
use std::cell::Cell;
use std::sync::Arc;
fn main() {
    let c = Arc::new(Cell::new(1));
    let c2 = Arc::clone(&c);
    std::thread::spawn(move || c2.set(2)).join().unwrap();
    println!("{}", c.get());
}

What does the compiler say?
  • aNothing; it compiles and prints 1 due to a lost update between the two threads
  • bNothing; it compiles and prints 2 after the join
  • cE0277: Arc<Cell<i32>> cannot be sent, since Arc is only Sync and not Send
  • dE0277: Cell<i32> cannot be shared between threads safely
Explanation:Arc<T> is Send only when T: Send + Sync, because every clone can reach the same T from a different thread. Cell allows mutation through a shared reference without synchronization, so it is !Sync. The error names Cell<i32> and uses the word "shared", which is the Sync failure wording; the Send wording is "sent". Wrapping the value in Mutex or using AtomicI32 fixes it.
Rs Concurrency Send SyncDifficulty 2
fn main() {
    let v = vec![1, 2, 3];
    let mut total = 0;
    std::thread::scope(|s| {
        s.spawn(|| { total = v.iter().sum(); });
    });
    println!("{} {:?}", total, v);
}

What is printed?
  • aNothing; E0373 is reported since the closure borrows v and total without move
  • b0 [1, 2, 3], since total is read before the scoped thread has finished its work
  • cNothing; E0499 is reported since total is mutably borrowed by the closure and by println!
  • d6 [1, 2, 3]; the scope joins its threads before returning
Explanation:thread::scope guarantees that every thread spawned inside it has been joined by the time scope returns. That guarantee is what lets scoped closures borrow non-'static data such as v and total. After the scope, total holds 6 and v is untouched. The plain thread::spawn API cannot offer this and therefore insists on 'static captures.
Rs Concurrency Send SyncDifficulty 1
fn main() {
    let h = std::thread::spawn(|| {
        panic!("boom");
    });
    let r = h.join();
    println!("is_err={}", r.is_err());
    println!("main continues");
}

What happens at runtime?
  • aThe panic propagates through join, so main panics before printing anything
  • bThe panic message goes to stderr; then is_err=true and main continues are printed, because a panic unwinds only its own thread
  • cThe whole process aborts the moment the spawned thread panics
  • dis_err=false is printed; a panic inside a thread counts as a normal return
Explanation:A panic unwinds only the thread it happens in. The spawned thread's panic hook still prints "thread '<unnamed>' panicked" to stderr, but main keeps running. JoinHandle::join returns Result<T, Box<dyn Any + Send>>, and the Err variant carries the panic payload, so is_err=true is printed and execution continues.
Rs Concurrency Send SyncDifficulty 2
use std::sync::{Arc, Mutex};
fn main() {
    let data = Arc::new(Mutex::new(0));
    let mut hs = vec![];
    for _ in 0..3 {
        hs.push(std::thread::spawn(move || {
            *data.lock().unwrap() += 1;
        }));
    }
    for h in hs { h.join().unwrap(); }
}

Why does this not compile?
  • aMutex<i32> is not Send, so the closure cannot go to thread::spawn
  • b+= needs a mutable guard, but lock() only returns a shared reference to the value
  • cdata is moved into the closure on the first iteration; E0382 on the next one
  • dArc cannot be captured by a move closure; it must be cloned outside main
Explanation:A move closure takes ownership of data. Since the closure is created inside a loop, the second iteration tries to move a value that was already moved, and the compiler reports E0382 ("value moved into closure here, in previous iteration of loop"). The idiomatic fix is let data = Arc::clone(&data); at the top of each iteration so that every thread owns its own handle to the shared mutex.

Test yourself against the 3750-question Backend bank.

Start interview