Efficient Competitive Programming Input
In competitive programming and high-throughput CLI benchmarks, problems frequently provide 100,000 to 1,000,000 whitespace-separated integers on stdin and expect hundreds of thousands of lines on stdout within a strict 1.0–2.0 second time limit. Using naive per-line io::stdin().read_line() and println!() calls often exceeds the time limit due to lock traffic, per-line heap allocations, and per-line stdout flushes.
Performance Bottlenecks of Naive I/O vs. Fast I/O Idioms
| Bottleneck in Naive Code | Fast Standard-Library Replacement | Speedup Factor |
|---|---|---|
Calling println! 200,000 times (flushes stdout on every \n!) |
Lock stdout once and wrap in BufWriter::new(io::stdout().lock()) with writeln! |
10x – 50x faster |
Calling stdin().lines() (locks mutex & heap-allocates a String per line) |
Read the entire input into one String via io::read_to_string(io::stdin()) and split with .split_ascii_whitespace() |
5x – 20x faster |
| Streaming huge inputs that cannot fit in RAM at once | Wrap io::stdin().lock() in a reusable token scanner over BufRead |
Constant $O(1)$ memory, zero lock overhead |
Pattern 1: Single-Allocation Full-Input Token Scanner (Fastest & Simplest)
Because competitive programming inputs almost always fit comfortably in RAM (1–30 MiB), reading all of stdin into a single String in one call (io::read_to_string) and iterating over tokens with split_ascii_whitespace() requires only 1 heap allocation for the entire program:
use std::io::{self, BufWriter, Write};
use std::str::FromStr;
struct FastScanner<'a> {
tokens: std::str::SplitAsciiWhitespace<'a>,
}
impl<'a> FastScanner<'a> {
fn new(input: &'a str) -> Self {
Self {
tokens: input.split_ascii_whitespace(),
}
}
fn next<T: FromStr>(&mut self) -> T {
self.tokens
.next()
.expect("unexpected EOF in input")
.parse()
.ok()
.expect("failed to parse token")
}
}
fn solve(input_text: &str, out: &mut impl Write) -> io::Result<()> {
let mut scan = FastScanner::new(input_text);
// Read number of test cases `t`:
let t: usize = scan.next();
for case_idx in 1..=t {
let a: i64 = scan.next();
let b: i64 = scan.next();
writeln!(out, "Case #{case_idx}: {}", a + b)?;
}
Ok(())
}
fn main() -> io::Result<()> {
// In a real contest submission:
// `let input = io::read_to_string(io::stdin())?;`
let simulated_stdin = "3\n 10 20 \n -5 105\n1000000000 2000000000\n";
let mut out = BufWriter::new(io::stdout().lock());
solve(simulated_stdin, &mut out)?;
out.flush()?;
Ok(())
}
Case #1: 30
Case #2: 100
Case #3: 3000000000
Pattern 2: Interactive Problems (flush() Required After Every Query!)
In interactive competitive programming problems (where your program prints a query and waits for the judge process to respond on stdin), wrapping stdout in BufWriter will deadlock unless you call out.flush()? immediately after writing each query!