diff --git a/crates/vp9/src/decoder.rs b/crates/vp9/src/decoder.rs
--- a/crates/vp9/src/decoder.rs
+++ b/crates/vp9/src/decoder.rs
@@ -930,7 +930,8 @@ fn assemble_mi(strips: &mut [Strip], mi_rows: usize, mi_cols: usize, pools: &Poo
         let s = &mut strips[0];
         return (MiGrid { cols: mi_cols, rows: mi_rows, mi: std::mem::take(&mut s.mi) }, std::mem::take(&mut s.seg_ids));
     }
-    let mut mi = pools.mi.take(mi_rows * mi_cols, MiInfo::default());
+    // Every position is copied from a strip below.
+    let mut mi = pools.mi.take_any(mi_rows * mi_cols, MiInfo::default());
     let mut seg = vec![0u8; mi_rows * mi_cols];
     for s in strips.iter_mut() {
         let mw = s.mi_col_end - s.mi_col_start;
diff --git a/crates/vp9/src/frame.rs b/crates/vp9/src/frame.rs
--- a/crates/vp9/src/frame.rs
+++ b/crates/vp9/src/frame.rs
@@ -246,30 +246,39 @@ impl<T> Pool<T> {
 impl<T: Clone> Pool<T> {
     /// A buffer of `len` elements with unspecified contents (recycled memory when available;
     /// `fill` only initialises fresh memory).
+    ///
+    /// Invariant for callers: every element that is read must have been written first. For the
+    /// sample planes and mode info grids this holds because every sample inside the mode info grid
+    /// is predicted before a later stage (loop filter, reference reads, intra edges) reads it, and
+    /// every mode info position is written by its tile before it is read. Reads are clamped to
+    /// that grid (intra edges to `max_x` / `max_y`, references to the visible size). A change that
+    /// reads outside the grid would see stale data from a recycled buffer.
     pub fn take_any(&self, len: usize, fill: T) -> Vec<T> {
         let found = {
             let mut b = self.bufs.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
-            b.iter().position(|v| v.len() >= len).map(|i| b.swap_remove(i))
-        };
-        match found {
-            Some(mut v) => {
-                v.truncate(len);
-                v
+            // Best fit by capacity: the first fit handed large buffers to small requests (band
+            // scratch rows, chroma bands), and once truncated they never served a large one
+            // again, so large requests kept allocating fresh (zeroed) memory.
+            let mut best: Option<(usize, usize)> = None;
+            for (i, v) in b.iter().enumerate() {
+                let c = v.capacity();
+                if c >= len && best.is_none_or(|(_, bc)| c < bc) {
+                    best = Some((i, c));
+                    if c == len {
+                        break;
+                    }
+                }
             }
-            None => vec![fill; len],
-        }
-    }
-
-    /// A buffer of `len` copies of `fill` (recycled memory when available).
-    pub fn take(&self, len: usize, fill: T) -> Vec<T> {
-        let found = {
-            let mut b = self.bufs.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
-            b.iter().position(|v| v.capacity() >= len).map(|i| b.swap_remove(i))
+            best.map(|(i, _)| b.swap_remove(i))
         };
         match found {
             Some(mut v) => {
-                v.clear();
-                v.resize(len, fill);
+                // Contents are unspecified: only memory beyond the current length is written.
+                if v.len() >= len {
+                    v.truncate(len);
+                } else {
+                    v.resize(len, fill);
+                }
                 v
             }
             None => vec![fill; len],
diff --git a/crates/vp9/src/tile.rs b/crates/vp9/src/tile.rs
--- a/crates/vp9/src/tile.rs
+++ b/crates/vp9/src/tile.rs
@@ -129,12 +129,15 @@ impl<'a> TileDecoder<'a> {
         let strip_h = sb_rows * 64;
         let x0 = mi_col_start * 8;
         let uv_len = (strip_w >> ss_x) * (strip_h >> ss_y);
-        let planes = [s.pools.samples.take(strip_w * strip_h, 0), s.pools.samples.take(uv_len, 0), s.pools.samples.take(uv_len, 0)];
+        // Every sample a later stage reads (inside the mode info grid) is predicted before use, so
+        // recycled planes need no clearing.
+        let planes = [s.pools.samples.take_any(strip_w * strip_h, 0), s.pools.samples.take_any(uv_len, 0), s.pools.samples.take_any(uv_len, 0)];
         let mi_w = mi_col_end - mi_col_start;
         let strip = Strip {
             mi_col_start,
             mi_col_end,
-            mi: s.pools.mi.take(mi_w * mi_rows, MiInfo::default()),
+            // Written for every position of the grid before it is read.
+            mi: s.pools.mi.take_any(mi_w * mi_rows, MiInfo::default()),
             seg_ids: vec![0; mi_w * mi_rows],
             planes,
             strides: [strip_w, strip_w >> ss_x, strip_w >> ss_x],
