Skip to content
Skilled.dev

Language preparation

Rust coding interview preparation: borrowing and collections

Write interview algorithms with borrowed slices, explicit Option results, HashMap entries, VecDeque, and min-heap priority through Reverse.

Published · 5 min read

Rust interview preparation needs an ownership plan alongside the algorithm. Decide which data the function reads, which state it owns, and which values must be copied into the result. That usually produces a simpler implementation than adding clones whenever the borrow checker reports a conflict.

The examples use stable standard-library features available with the Rust 2021 edition. They return owned numeric results and borrow the input slices. Exercise harnesses may supply their own node types or signatures; use those published interfaces rather than replacing them with an unrelated tree model.

Borrow input and own the working state

A parameter such as values: &[i64] lets the function read a borrowed slice without taking ownership of the caller's vector. Its local map or heap belongs to the function. A returned pair of indexes contains Copy values, so it needs no lifetime tied to the borrowed input.

Iterating with values.iter().enumerate() produces an index and a reference to each value. The pattern &value copies an i64 out of that reference. This is inexpensive fixed-size value copying, distinct from cloning a string, vector, or reference-counted graph.

For a mutable backtracking path, push a choice, recurse, then pop it. Saving the path requires an owned snapshot if later mutations must not change earlier results. Cloning the working vector at a completed answer can be correct and necessary; cloning it at every intermediate call may allocate much more than the output contract requires.

Return Option and handle arithmetic range

Two Sum returns Option<(usize, usize)>. A None result is different from an index-zero success, and a successful tuple always has two positions. The map lookup happens before storing the current value, preserving the distinct-position rule.

use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap};

fn two_sum(values: &[i64], target: i64) -> Option<(usize, usize)> {
    let mut previous: HashMap<i64, usize> = HashMap::new();
    for (index, &value) in values.iter().enumerate() {
        if let Some(complement) = target.checked_sub(value) {
            if let Some(&earlier) = previous.get(&complement) {
                return Some((earlier, index));
            }
        }
        previous.insert(value, index);
    }
    None
}

fn largest_k(values: &[i64], k: usize) -> Vec<i64> {
    if k == 0 { return Vec::new(); }
    let mut retained: BinaryHeap<Reverse<i64>> = BinaryHeap::new();
    for &value in values {
        retained.push(Reverse(value));
        if retained.len() > k { retained.pop(); }
    }
    retained.into_iter().map(|Reverse(value)| value).collect()
}

An overflowing complement cannot equal any i64 value in the earlier-input map, so that iteration can safely skip lookup and still insert the current value. checked_sub expresses this case without relying on a build profile's overflow behavior. Tests should include i64 extremes as well as small duplicates and missing answers.

The map solution uses expected O(n) time under the usual hash-operation model and O(n) additional entries. Hashing and equality can cost more for long or composite keys; the numeric-key model here keeps those operations bounded. The HashMap reference documents its lookup, entry, and key contracts.

Choose heap orientation from the eviction rule

Rust's BinaryHeap is a max-heap by default. Wrapping values in Reverse makes the smallest numeric value the highest-priority element. In the top-K function, that element is the weakest retained candidate and therefore the one to discard when the heap grows past K.

The code temporarily retains at most K + 1 values, then removes the smallest. Equal values remain separate arrivals. If K exceeds the input length, every value is returned. The final vector has heap iteration order, not sorted order; sort it separately if the requested output requires ordering.

Selection takes O(n log(K + 1)) comparison work for positive K, with O(min(n, K)) retained elements after each completed update. Allocation growth has its own cost. The empty-K branch returns immediately without examining the input. Read the BinaryHeap reference for its priority and allocation guarantees, and compare with the heap pattern guide.

Keep mutable and shared borrows short

A map's get returns a shared reference into the map. Mutating that map while still needing the reference can conflict with the borrow. When the mapped value is Copy, extracting an owned index as the example does often ends the lookup's useful borrow before insertion.

For frequencies, the entry API can provide one mutable reference to the mapped count: *counts.entry(value).or_insert(0) += 1. That fits a single-key update. It does not automatically solve an operation that needs several simultaneous mutable references to related entries.

A borrow-checker error can reveal an unclear state transition. Identify which value you need after mutation and whether you can copy that value, split the operation, or change the representation. Replacing every borrowed value with a clone may compile while concealing unnecessary allocation or an ownership model that no longer matches the intended graph.

Use VecDeque for FIFO traversal

A Vec supports stack operations efficiently at its end. Removing index zero shifts remaining elements and is a poor default for a growing BFS queue. VecDeque supplies push-back and pop-front operations for FIFO processing.

A graph with integer labels can use a visited HashSet and a VecDeque of copied labels. Mark a neighbor when inserting it, so a cycle or a second parent does not queue it again. For owned String labels, decide whether the traversal owns cloned labels or borrows names from a stable graph representation. Include those string copies in the memory discussion.

A proper tree exercise may supply Option<Box<Node>>, borrowed nodes, or shared nodes through Rc<RefCell<Node>>. Those representations support different mutation and sharing operations. Start with the exercise's type, define the recursive return value, and avoid introducing interior mutability solely to reproduce a template written for another representation.

Clarify UTF-8 and indexing

A Rust String contains valid UTF-8, and its byte length differs from its number of Unicode scalar values. Ordinary integer indexing cannot retrieve a character from a string. Iterating with chars visits scalar values, while char_indices also supplies byte offsets.

Collecting characters into a vector gives indexed access at the cost of allocation. Slicing text by byte boundaries requires valid character boundaries; an arbitrary byte index can panic. Scalar values still do not define user-perceived grapheme clusters. For ASCII-only interview input, state the constraint and use byte processing deliberately.

Verify ownership and the result contract

Compare Two Sum with exhaustive pairs using widened reference arithmetic so the test itself does not overflow. Check that None agrees with no valid pair and that any returned indexes are distinct, in bounds, and sum to the target mathematically. Include minimum and maximum i64 values and a valid later pair after an earlier complement overflow.

For top-K, sort a copy of the input in descending order and compare its first K values with a sorted result from the heap. Include K zero, K larger than n, negative values, and duplicates. Then practice permutation generation, BFS, and a full solution explanation that names which collections own their data and which inputs remain borrowed.

Worked lesson walkthroughs

58 reading lessons with code examples in this language.

Concepts

Concept lesson

Arrays in Rust

Learn array insertion in Rust. Trace a copied result, explain shifting costs, and check empty inputs and endpoint positions without changing the original.

Concept lesson

Big O time and space complexity in Rust

Analyze time and space complexity in Rust with a suffix-copy example. Count nested work, returned storage, and allocation instead of counting loops alone.

Concept lesson

Hash Tables in Rust

Count frequencies with a hash map in Rust. Preserve input order, distinguish missing keys from zero, and explain expected lookup time and retained storage.

Concept lesson

Strings in Rust

Practice string construction in Rust with a repeat-text example. Explain builders, copies, Unicode representation, output size, and boundary cases.

Concept lesson

Palindromes, permutations, and anagrams in Rust

Check an ASCII-normalized palindrome in Rust. Trace two-pointer comparisons, case handling, ignored characters, and the limits of the text contract.

Concept lesson

Recursion in Rust

Trace recursive factorial in Rust from the base case through returning calls. Explain termination, stack space, integer limits, and boundary tests.

Concept lesson

Logarithmic time in Rust: repeated halving and search

Understand logarithmic loops in Rust by counting integer halvings. Test powers of two, explain rounding, and derive the exact step count.

Concept lesson

Binary search in Rust: invariants and boundary tests

Write first-occurrence binary search in Rust. Trace half-open bounds, duplicate values, missing targets, and the random-access assumption behind O(log n).

Concept lesson

Sorting in Rust

Return a numerically sorted copy in Rust. Preserve duplicates and the original input, choose the right library operation, and account for copied storage.

Concept lesson

Linked Lists in Rust

Find a linked list's middle in Rust with slow and fast runners. Define the even-length result, handle an empty list, and separate construction from traversal.

Concept lesson

Pointers in Rust

Trace shared mutation and reference rebinding in Rust. Follow two aliases through one object update and a new allocation, with explicit ownership notes.

Concept lesson

Queues in Rust

Practice FIFO queue behavior in Rust. Compare efficient front removal with a head cursor, preserve repeated values, and count fill-and-drain costs.

Concept lesson

Stacks in Rust

Practice LIFO stack behavior in Rust. Trace push and pop, separate empty state from zero payloads, and explain node ownership and end-operation costs.

Concept lesson

Trees in Rust

Compute binary-tree edge height in Rust. Read heap-indexed nullable slots, handle an empty root, and account for reachable nodes and recursion depth.

Concept lesson

Tree traversal in Rust: in-order, pre-order, and post-order

Trace in-order binary-tree traversal in Rust. Explain nullable child positions, visit order, recursion space, and when the output is actually sorted.

Concept lesson

Binary Search Trees in Rust

Search a binary search tree in Rust. Follow one ordered path, handle absent children, and relate search cost to height without assuming balance.

Concept lesson

Graphs in Rust

Read graph adjacency lists in Rust and compute out-degrees. Define vertex IDs, parallel edges, self-loops, representation costs, and input assumptions.

Concept lesson

Depth-First Search in Rust

Implement iterative DFS in Rust. Preserve listed neighbor order, skip repeated visits, trace a shared descendant, and explain pending-stack costs.

Concept lesson

Breadth-First Search in Rust

Implement BFS in Rust with FIFO order and enqueue-time discovery. Trace graph layers, cycles, shared descendants, and visited-storage costs.

Concept lesson

Subproblems and Overlapping Subproblems in Rust

Count naive Fibonacci calls in Rust. Trace overlapping states, include base-case work, and identify the cache key before applying memoization.

Concept lesson

Memoization in Rust

Memoize Fibonacci in Rust and count distinct computed states. Preserve cached zero, define cache lifetime, and explain remaining recursion depth.

Concept lesson

Bottom-Up vs. Top-Down in Rust

Build a bottom-up Fibonacci table in Rust. Follow dependency order, test zero input, and compare full-table storage with rolling-state alternatives.

Concept lesson

Dynamic Programming in Rust

Solve minimum coin count in Rust. Trace the recurrence, represent unreachable states, test a greedy counterexample, and explain amount-based table costs.

Data structure implementations

Implementation tutorial

Implement a dynamic array in Rust

Implement a map-backed teaching array in Rust. Trace push, pop, insertion, and removal, including logical length, missing indices, and obsolete entries.

Implementation tutorial

Implement a hash table in Rust: collisions, updates, and removal

Build a hash table in Rust with separate chaining. Follow colliding keys through lookup, update, and deletion, and explain the limits of a fixed teaching hash.

Implementation tutorial

Implement a linked list in Rust: head, tail, and node identity

Build a doubly linked list in Rust. Trace prepend and append, verify reciprocal links and both endpoints, and explain ownership and retention.

Implementation tutorial

Implement a queue in Rust: FIFO links and empty transitions

Implement a linked queue in Rust. Trace FIFO order, the last removal, and refilling an empty queue while preserving first and last endpoint invariants.

Implementation tutorial

Implement a stack in Rust: push, pop, and ownership

Implement a linked stack in Rust. Follow the top link through push, peek, and pop, including empty state, returned nodes, and retained chains.

Implementation tutorial

Implement a binary search tree in Rust: insert and search

Build a binary search tree in Rust. Trace insertion and search, preserve duplicate keys on the right, and test skewed shapes without assuming balancing.

Implementation tutorial

Implement a graph in Rust: adjacency lists and edge removal

Build an adjacency-list graph in Rust. Trace directed and undirected edges, self-loops, parallel entries, and removal of incoming and outgoing edges.

Practice problems

Practice walkthrough

Two Sum in Rust: all index pairs with HashMap and checked conversions

Use Rust HashMap to return every matching index pair. Handle borrowed lookups, checked narrowing, original indexes, and the course result type.

Practice walkthrough

Valid brackets in Rust: ASCII bytes and a Vec stack

Validate ASCII brackets in Rust with byte iteration and Vec::pop. Handle Option safely, ignore other text, and test deep nesting without recursion.

Practice walkthrough

Product of array except self in Rust: owned vectors and prefix products

Build products without division in Rust using an output Vec<i64>. Explain ownership, reverse ranges, integer bounds, zeros, and input allocation.

Practice walkthrough

Counting sort for ages in Rust: worked solution

Sort bounded ages with a frequency table, preserving duplicate values. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Best stock profit in Rust: one transaction

Find the best single chronological stock trade with one scan. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Most frequent email in Rust: hash-map solution

Count email occurrences and return the unique most frequent address. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

First Bad Version in Rust: boundary binary search

Find the first true value in a monotone version predicate. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Reverse a linked list in Rust: pointer walkthrough

Reverse a linked list by moving nodes from an untouched suffix to a reversed prefix. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Remove kth node from end in Rust: zero-based deletion

Delete the zero-based kth node from the end of a linked list. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge sorted linked lists in Rust: Combine Blockchains

Merge two sorted linked chains while preserving duplicate timestamps. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count Islands in Rust: DFS flood-fill walkthrough

Count side-connected land components with a destructive flood fill of the working grid. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate a binary search tree in Rust: inherited bounds

Validate every binary-tree node against bounds inherited from all its ancestors. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Binary tree maximum depth in Rust: count edges

Measure a binary tree’s longest root-to-leaf path in edges. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Lowest common ancestor in Rust: return the original node

Find the lowest common ancestor in a general binary tree and return the original node. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Generate string permutations in Rust: positional results

Generate positional string permutations, retaining repeated results for repeated characters. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Group palindrome permutations in Rust: counts and keys

Filter palindrome permutations, then group retained strings by character multiset. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge intervals in Rust: Interview Scheduler

Merge sorted interval coverage with an explicit touching-endpoint rule. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Min-max stack in Rust: constant-time extrema

Support stack extrema through histories that retain duplicate record values. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

BFS shortest path in Rust: Pilot a Rocket

Find an unweighted shortest route and reconstruct it from predecessor links. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Maze reachability in Rust: memoized down/right search

Memoize directed maze reachability while keeping unknown distinct from false. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a rotated sorted array in Rust: Shifted Search

Search a distinct rotated sorted array by identifying an ordered half. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a sorted matrix in Rust: staircase walkthrough

Eliminate a row or column at each step in a row-and-column sorted matrix. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

House Robber in Rust: maximum nonadjacent sum

Maximize a nonadjacent selection with two rolling prefix optima. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Sum decimal digits in Rust: recursive solution

Sum decimal digits through a decreasing recursive integer decomposition. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count 1, 2, or 3-step paths in Rust: Three Moves

Count ordered one-, two-, and three-step routes with rolling dynamic programming. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Coin change in Rust: minimum-coin dynamic programming

Minimize unlimited coin usage with a bottom-up amount table. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate Sudoku in Rust: rows, columns, and subsquares

Validate filled Sudoku entries against rows, columns, and subsquares. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Spiral matrix traversal in Rust: Walk a Matrix

Traverse a rectangle in clockwise spiral order using shrinking boundaries. Follow the Rust implementation, its correctness argument, worked example, complexity, and boundary tests.