Final Exam Question Bank
This file contains the question bank for the final exam.
There are 5 types of questions, which are worth the following points:
- select all that are true = 2 points
- short answer = 2 points
- predict the output = 3 points
- short coding challenge = 4 points
- long coding challenge = 8 points
Final Exam Format
The final exam will be organized into five parts:
- Part 1: 8 points, 4 questions, 2 points each -- select all that are true
- Part 2: 8 points, 4 questions, 2 points each -- short answer questions
- Part 3: 12 points, 4 questions, 3 points each -- predict the output and explain why
- Part 4: 8 points, 2 questions, 4 points each -- short coding challenges
- Part 5: 16 points, 2 questions, 8 points each -- longer coding challenges
The final exam will be worth 52 points and the questions will be sampled from this question bank.
You'll have 2 hours (120 minutes) to complete the exam.
The exam is closed notes, no electronic devices, but you will be given the following final_exam_reference_material.md at the exam.
Select All That Are True (mcq_multi)
Select all statements that are true about ownership and moves in Rust:
-
A. Assigning a
Stringto another variable moves ownership; the original variable is no longer valid. -
B. Passing a
&String(a shared reference) to a function moves ownership of theStringinto the function. -
C. Assigning an
i32to another variable moves ownership; the original variable is no longer valid. - D. A value is dropped when its owner goes out of scope.
Select all statements that are true about borrowing rules at a single point in a program:
-
A. You can have any number of immutable references (
&T) simultaneously. -
B. You can have one mutable reference (
&mut T) and one immutable reference simultaneously. - C. You can have at most one mutable reference at a time.
-
D. A mutable reference
&mut Tgives you read-only access to the value it points at.
Select all statements that are true about traits in Rust:
- A. A trait defines a set of method signatures (and optionally default implementations) that types can implement.
- B. You can implement a trait you did not define on a type you did not define.
- C. A single type can implement multiple traits.
- D. A trait method can have a default implementation that implementors may override.
Select all statements that are true about #[derive(...)] in Rust:
-
A.
#[derive(Debug)]lets you print a value with{:?}. -
B. To derive
Copy, the type must also derive (or implement)Clone. -
C. You can derive
Copyon a struct that has aStringfield. -
D. To use a struct as a
HashMapkey, the struct typically needs to deriveEqandHash.
Select all statements that are true about lifetimes in Rust:
- A. Lifetimes describe how long references are valid and are checked at compile time.
- B. Lifetime annotations change how long a value lives at runtime.
-
C. A function signature like
fn longest<'a>(x: &'a str, y: &'a str) -> &'a strsays the returned reference is valid for at least as long as both inputs. - D. Every function that takes a reference parameter must include an explicit lifetime annotation written out by the programmer.
Select all statements that are true about generics in Rust:
-
A.
fn largest<T: PartialOrd>(list: &[T]) -> &Tworks for any typeTthat can be ordered. - B. Generics are resolved at runtime through dynamic dispatch by default.
-
C. A trait bound like
T: Clone + Debugmeans thatTmust implement at least one ofCloneorDebug. - D. Generic functions always rely on runtime type information (RTTI) to decide which type-specific code to execute.
Select all statements that are true about iterators in Rust:
-
A. Calling
.map(...)on an iterator does the work immediately. -
B. Iterator adaptors like
filter,map, andtakeare lazy — no work happens until a consuming method runs. -
C.
.collect()is a consuming method that drives the iterator to completion. -
D.
.sum()is a consuming method that returns the total of all elements.
Select all statements that are true about closures in Rust:
- A. A closure can capture variables from its enclosing scope by reference, by mutable reference, or by move.
-
B. The
movekeyword forces the closure to take ownership of captured variables. -
C. A closure and a regular
fnfunction have exactly the same type in Rust. -
D. Closures are commonly passed to iterator methods such as
map,filter, andfold.
Select all statements that are true about Option<T> and Result<T, E>:
-
A.
Option<T>represents a value that may or may not be present. -
B.
Result<T, E>represents either a success value of typeTor an error of typeE. -
C. Calling
.unwrap()onNonereturns the default value of typeTrather than panicking. -
D.
matchcannot be used to destructureOptionorResultvalues.
Select all statements that are true about HashMap<K, V>:
-
A. Keys must implement
EqandHash. - B. Iteration order is guaranteed to match insertion order.
-
C. Average
insert,get, andremoveare O(1). -
D. Calling
inserton an existing key replaces the old value and returnsSome(old_value).
Select all statements that are true about BTreeMap<K, V>:
-
A. Keys must implement
Ord. - B. Iteration visits entries in insertion order.
-
C.
insert,get, andremoveare O(1) average-case, just likeHashMap. -
D.
first_key_value()returns the entry that was inserted first in time.
Select all statements that are true about HashSet<T>:
-
A. A
HashSetstores unique values — inserting an existing value has no effect on set membership. -
B.
insertreturnstrueif the value was newly inserted andfalseif it was already present. -
C.
HashSetiteration is guaranteed to visit elements in the order they were inserted. -
D.
HashSetkeeps its elements in sorted order.
Select all statements that are true about BinaryHeap<T> in Rust:
-
A. By default,
BinaryHeap<T>is a max-heap:pop()returns the largest element. -
B. Elements only need to implement
PartialOrd, so aBinaryHeap<f64>compiles and works as-is. -
C. Iterating with
.iter()returns elements in sorted order. -
D.
BinaryHeap::peek()removes and returns the largest element.
Select all statements that are true about stacks and queues in Rust:
-
A. A
Vec<T>can be used as a stack viapush/pop, with both operations running in amortized O(1). - B. Stacks are LIFO (last-in, first-out) and queues are FIFO (first-in, first-out).
-
C.
VecDeque<T>supports efficientpush_backandpop_front, making it a natural FIFO queue. -
D.
Vec::remove(0)is an O(1) operation.
Select all statements that are true about the (average-case) complexity of common operations:
-
A. Inserting at the front of a
Vec<T>withvec.insert(0, x)is O(1). -
B.
HashMap::getis O(1) on average. -
C.
BTreeMap::getis O(1) on average, just likeHashMap::get. -
D. Looking up an element by value in an unsorted
Vec<T>with.contains(&x)is O(log n).
Select all statements that are true about the Entry API on HashMap and BTreeMap:
-
A.
map.entry(key).or_insert(default)insertsdefaultonly if the key is absent, and returns a mutable reference to the value. -
B.
map.entry(key).or_insert(default)always overwrites the existing value when the key is already present. -
C. The
EntryAPI is available only onHashMap;BTreeMapdoes not provide anentrymethod. -
D. Internally,
map.entry(key).or_insert(v)performs two separate lookups (one to check for the key, one to insert), so it is slower than a manualget+insert.
Short Answer (short_answer)
Why does Rust have an ownership system? Name one problem it prevents at compile time that languages like C or Python handle differently.
What is a trait in Rust? Give one concrete example of a standard-library trait and describe what it lets you do.
What does a lifetime annotation like 'a in fn longest<'a>(x: &'a str, y: &'a str) -> &'a str tell the Rust compiler?
Why are generics useful in Rust? Give one example.
What does it mean that iterator adaptors in Rust are "lazy"? Give an example of an adaptor and an example of a consumer.
How does a closure in Rust differ from a regular fn function?
What is the difference between Option<T> and Result<T, E>, and when would you reach for each?
What is the role of the hash function when using a HashMap<K, V>, and why must keys implement Eq as well as Hash?
When would you choose BTreeMap<K, V> over HashMap<K, V>? Name one specific advantage.
Give one concrete use case where a HashSet<T> is a better choice than a Vec<T>, and briefly explain why.
What is BinaryHeap<T> useful for? Describe one typical use case and the order in which pop() returns elements.
Explain the difference between a stack and a queue. Which Rust standard-library type is a good fit for each?
Describe the difference between O(1), O(n), and O(log n) time complexity in plain language. Give one Rust collection operation for each.
What problem does the Entry API on HashMap / BTreeMap solve, compared with using contains_key followed by insert?
Name three commonly derived traits in Rust and briefly describe what #[derive(...)] gives you for each.
Give one reason why Vec<T> is usually preferred over LinkedList<T> in Rust, even when you need to frequently grow a collection.
Predict the Output (predict_output)
What is the output of this program?
fn main() { let x = 5; let y = x; let x = x + 1; println!("{} {}", x, y); }
What is the output of this program?
fn double<T: std::ops::Add<Output = T> + Copy>(x: T) -> T { x + x } fn main() { println!("{} {}", double(3), double(2.5)); }
What is the output of this program?
fn main() { let a: Option<i32> = Some(7); let b: Option<i32> = None; println!("{} {}", a.unwrap_or(0), b.unwrap_or(0)); }
What is the output of this program?
fn main() { let total: i32 = (1..=5) .filter(|x| x % 2 == 1) .map(|x| x * x) .sum(); println!("{}", total); }
What is the output of this program?
fn main() { let names = vec!["Alice", "Bob", "Carol"]; let scores = vec![90, 85, 95]; for (i, (n, s)) in names.iter().zip(scores.iter()).enumerate() { println!("{}: {} = {}", i, n, s); } }
What is the output of this program?
fn main() { let greeting = String::from("Hello"); let say = move || println!("{}, world!", greeting); say(); say(); }
What is the output of this program?
#[derive(Debug)] struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 1, y: 2 }; println!("{:?}", p); }
What is the output of this program?
use std::collections::HashMap; fn main() { let mut map: HashMap<&str, i32> = HashMap::new(); map.insert("a", 1); let prev = map.insert("a", 2); println!("{:?} {:?}", prev, map.get("a")); }
What is the output of this program?
use std::collections::HashMap; fn main() { let mut counts: HashMap<char, i32> = HashMap::new(); for c in "banana".chars() { *counts.entry(c).or_insert(0) += 1; } let mut items: Vec<(char, i32)> = counts.into_iter().collect(); items.sort(); println!("{:?}", items); }
What is the output of this program?
use std::collections::HashSet; fn main() { let mut seen: HashSet<i32> = HashSet::new(); println!("{}", seen.insert(1)); println!("{}", seen.insert(2)); println!("{}", seen.insert(1)); }
What is the output of this program?
use std::collections::BTreeMap; fn main() { let mut scores: BTreeMap<&str, i32> = BTreeMap::new(); scores.insert("carol", 95); scores.insert("alice", 90); scores.insert("bob", 85); for (name, score) in &scores { println!("{} {}", name, score); } }
What is the output of this program?
use std::collections::BinaryHeap; fn main() { let mut heap: BinaryHeap<i32> = BinaryHeap::new(); for x in [3, 1, 4, 1, 5, 9, 2] { heap.push(x); } while let Some(top) = heap.pop() { print!("{} ", top); } println!(); }
What is the output of this program?
use std::collections::VecDeque; fn main() { let mut q: VecDeque<i32> = VecDeque::new(); q.push_back(1); q.push_back(2); q.push_front(0); q.push_back(3); while let Some(x) = q.pop_front() { print!("{} ", x); } println!(); }
What is the output of this program?
fn classify(x: i32) -> &'static str { match x { n if n < 0 => "negative", 0 => "zero", 1..=9 => "single digit", _ => "big", } } fn main() { for x in [-1, 0, 7, 42] { println!("{}: {}", x, classify(x)); } }
What is the output of this program?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y } } fn main() { let s1 = String::from("hello"); let s2 = String::from("ds210"); let result = longest(&s1, &s2); println!("{}", result); }
What is the output of this program?
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let r = Rectangle { width: 3, height: 4 }; println!("{}", r.area()); }
Short Coding Challenges (coding + short tag)
Write a function char_count(s: &str) -> HashMap<char, usize> that returns a map from each character in s to the number of times it appears.
Call it in main with "rust" and print the resulting map with {:?}.
In main, start with let nums = vec![1, 2, 2, 3, 1, 4, 3, 5]; and use a HashSet<i32> to count how many distinct values appear. Print the count.
In main, build a BTreeMap<&str, i32> from the pairs ("banana", 3), ("apple", 5), ("cherry", 2), and then iterate and print each name score pair on its own line.
In main, use a BinaryHeap<i32> to find and print the three largest values in vec![4, 10, 2, 8, 15, 7, 1], in descending order, separated by spaces.
In main, use VecDeque<i32> as a FIFO queue. Enqueue the numbers 1 through 5 (in order), then dequeue them all and print each dequeued value on its own line.
In main, use iterator methods to compute and print the sum of squares of the even numbers in 1..=10.
In main, given vec![3, 1, 4, 1, 5, 9, 2, 6, 5], use .partition(...) to split the values into two Vec<i32>s: those less than 5 and those greater than or equal to 5. Then print the lengths of the two resulting vectors.
Given let fruits = vec!["apple", "banana", "apple", "cherry", "banana", "apple"];, in main build a HashMap<&str, i32> using the entry API that inserts each key and increments the count for each key and print how many times "apple" appears.
Write a function first_long<'a>(words: &'a [&'a str], min_len: usize) -> Option<&'a str> that returns the first word in words whose length is at least min_len, or None if there isn't one.
Call it from main with &["hi", "ok", "rust", "hello"] and min_len = 4, and print the result using {:?}.
Write a generic function largest<T: PartialOrd + Copy>(list: &[T]) -> T that returns the largest element of the slice. Assume the slice is non-empty.
Call it from main once with &[10, 3, 7, 25, 4] and once with &[1.5_f64, 2.5, 0.5] and print both results.
In main, start with let mut scores = vec![("alice", 85), ("bob", 92), ("carol", 78)]; and use sort_by with a closure to sort the vector by score in descending order. Print the resulting vector with {:?}.
Long Coding Challenges (coding + long tag)
Write a program that:
- counts word frequencies in a sentence and
- prints the three most frequent words, along with their counts, in descending order of frequency.
Use the sentence "the quick brown fox jumps over the lazy dog the fox runs fast over the hill". Split on whitespace to get words (do not worry about punctuation or case). Break ties by alphabetical order of the word.
Expected output:
the 4
fox 2
over 2
Write a program that prints, in sorted order, the values that appear in both input vectors:
#![allow(unused)] fn main() { let a = vec![1, 2, 3, 4, 5, 6]; let b = vec![4, 5, 6, 7, 8, 9]; }
Use HashSet for the membership checks, then sort the resulting common values before printing them (one per line).
Expected output:
4
5
6
Build a small "grade book"
- Given
let entries = vec![("carol", 72), ("alice", 95), ("bob", 58), ("alice", 88), ("bob", 80)];, - insert each pair into a
BTreeMap<&str, Vec<i32>>keyed by name, with the list of that student's scores as the value. - Then iterate the map (which will give names in alphabetical order)
- and print each student's name followed by their average score, formatted to one decimal place.
Hint: To print 1 decimal place, use {:.1} in the println! macro.
Expected output:
alice 91.5
bob 69.0
carol 72.0
Write:
- a function
kth_largest(nums: &[i32], k: usize) -> Option<i32>that returns thek-th largest value in the slice (1-indexed). - If
kis zero or larger than the slice length, returnNone. - Use a
BinaryHeapto find the answer - Call the function from
mainwith&[7, 2, 9, 4, 11, 3]andk = 3 - Print the result with
{:?}.
Expected output:
Some(7)
Write a function is_balanced(s: &str) -> bool that returns true if every opening bracket in s is matched by a closing bracket of the same type in the correct order. Support (), [], and {}. Ignore any other characters.
Use a Vec<char> as a stack. Call the function from main on each of these inputs and print the result on its own line:
#![allow(unused)] fn main() { let cases = ["()[]{}", "([{}])", "(]", "((()"]; }
Expected output:
true
true
false
false
Implement the following:
- Write a function
moving_avg(values: &[f64], window: usize) -> Vec<f64>that computes the rolling average over a sliding window of sizewindow. - Use a
VecDeque<f64>to track the window. - Only emit an average once the window is fully populated, so the output length is
values.len() - window + 1(or empty ifvalues.len() < window).**
Call the function from main with values = &[1.0, 2.0, 3.0, 4.0, 5.0] and window = 3, and print each result on its own line, formatted to one decimal place.
Expected output:
2.0
3.0
4.0
Implement the following article and tweet structs and trait:
- Define a trait
Summarywith a single methodsummary(&self) -> String. - Define two structs:
Tweet { user: String, text: String }andArticle { title: String, body: String }. - Implement
Summaryfor the two structs.- The tweet summary should be
"@user: text", and - the article summary should be
"title — first 20 chars of body"- (use
.chars().take(20).collect::<String>()to get the first 20 characters)
- (use
- The tweet summary should be
- In
main, create:- A
Tweetwith user"alice"and text"hello rust!" - An
Articlewith title"DS210"and body"Rust is a systems language." - Call the
summarymethod for each and print the result.
- A
Hint: Use the format! macro to create the summary strings.
Expected output:
@alice: hello rust!
DS210 — Rust is a systems language.
Given the slice of (city, temp_f) pairs:
#![allow(unused)] fn main() { let readings = vec![ ("Boston", 48.0_f64), ("Phoenix", 102.5), ("Denver", 77.0), ("Miami", 88.0), ("Anchorage", 36.0), ]; }
Write a program that:
- prints every city whose temperature is above 75°F,
- with its temperature converted to Celsius (formula:
(f - 32.0) * 5.0 / 9.0), - one per line.
- with its temperature converted to Celsius (formula:
- Sort the output alphabetically by city.
- Use iterator methods (
filter,map,collect,sort_by_key, ...) rather than explicit loops.
Each printed line should look like Phoenix 39.2 (temperature formatted to one decimal place).
Define a struct Rectangle { width: u32, height: u32 } with three methods:
area(&self) -> u32perimeter(&self) -> u32can_hold(&self, other: &Rectangle) -> bool— returnstrueifselfis strictly larger thanotherin both dimensions
In main, create let a = Rectangle { width: 10, height: 5 }; and let b = Rectangle { width: 4, height: 3 };. Print a.area(), a.perimeter(), and a.can_hold(&b) each on their own line.
Implement the following:
- You are given
let scores = vec![("alice", 90), ("bob", 75), ("alice", 82), ("carol", 88), ("bob", 91), ("alice", 79)]; - Using the
EntryAPI on aHashMap<&str, (i32, i32)>(where the value is(total_points, count)), - compute each student's average.
- Then print a line for each student in alphabetical order, formatted
name avgwith the average rounded to one decimal place.
Expected output:
alice 83.7
bob 83.0
carol 88.0