diff --git a/library/core/src/iter/adapters/map_windows.rs b/library/core/src/iter/adapters/map_windows.rs
new file mode 100644
index 0000000000000..3c0e80b2559db
--- /dev/null
+++ b/library/core/src/iter/adapters/map_windows.rs
@@ -0,0 +1,293 @@
+use crate::{
+    fmt,
+    iter::{ExactSizeIterator, FusedIterator},
+    mem::{self, MaybeUninit},
+    ptr,
+};
+
+/// An iterator over the mapped windows of another iterator.
+///
+/// This `struct` is created by the [`Iterator::map_windows`]. See its
+/// documentation for more information.
+#[must_use = "iterators are lazy and do nothing unless consumed"]
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+pub struct MapWindows<I: Iterator, F, const N: usize> {
+    f: F,
+    inner: MapWindowsInner<I, N>,
+}
+
+struct MapWindowsInner<I: Iterator, const N: usize> {
+    // We fuse the inner iterator because there shouldn't be "holes" in
+    // the sliding window. Once the iterator returns a `None`, we make
+    // our `MapWindows` iterator return `None` forever.
+    iter: Option<I>,
+    // Since iterators are assumed lazy, i.e. it only yields an item when
+    // `Iterator::next()` is called, and `MapWindows` is not an exception.
+    //
+    // Before the first iteration, we keep the buffer `None`. When the user
+    // first call `next` or other methods that makes the iterator advance,
+    // we collect the first `N` items yielded from the inner iterator and
+    // put it into the buffer.
+    //
+    // When the inner iterator has returned a `None` (i.e. fused), we take
+    // away this `buffer` and leave it `None` to reclaim its resources.
+    //
+    // FIXME: should we shrink the size of `buffer` using niche optimization?
+    buffer: Option<Buffer<I::Item, N>>,
+}
+
+// `Buffer` uses two times of space to reduce moves among the iterations.
+// `Buffer<T, N>` is semantically `[MaybeUninit<T>; 2 * N]`. However, due
+// to limitations of const generics, we use this different type. Note that
+// it has the same underlying memory layout.
+struct Buffer<T, const N: usize> {
+    // Invariant: `self.buffer[self.start..self.start + N]` is initialized,
+    // with all other elements being uninitialized. This also
+    // implies that `self.start <= N`.
+    buffer: [[MaybeUninit<T>; N]; 2],
+    start: usize,
+}
+
+impl<I: Iterator, F, const N: usize> MapWindows<I, F, N> {
+    pub(in crate::iter) fn new(iter: I, f: F) -> Self {
+        assert!(N != 0, "array in `Iterator::map_windows` must contain more than 0 elements");
+
+        // Only ZST arrays' length can be so large.
+        if mem::size_of::<I::Item>() == 0 {
+            assert!(
+                N.checked_mul(2).is_some(),
+                "array size of `Iterator::map_windows` is too large"
+            );
+        }
+
+        Self { inner: MapWindowsInner::new(iter), f }
+    }
+}
+
+impl<I: Iterator, const N: usize> MapWindowsInner<I, N> {
+    #[inline]
+    fn new(iter: I) -> Self {
+        Self { iter: Some(iter), buffer: None }
+    }
+
+    fn next_window(&mut self) -> Option<&[I::Item; N]> {
+        let iter = self.iter.as_mut()?;
+        match self.buffer {
+            // It is the first time to advance. We collect
+            // the first `N` items from `self.iter` to initialize `self.buffer`.
+            None => self.buffer = Buffer::try_from_iter(iter),
+            Some(ref mut buffer) => match iter.next() {
+                None => {
+                    // Fuse the inner iterator since it yields a `None`.
+                    self.iter.take();
+                    self.buffer.take();
+                }
+                // Advance the iterator. We first call `next` before changing our buffer
+                // at all. This means that if `next` panics, our invariant is upheld and
+                // our `Drop` impl drops the correct elements.
+                Some(item) => buffer.push(item),
+            },
+        }
+        self.buffer.as_ref().map(Buffer::as_array_ref)
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let Some(ref iter) = self.iter else { return (0, Some(0)) };
+        let (lo, hi) = iter.size_hint();
+        if self.buffer.is_some() {
+            // If the first `N` items are already yielded by the inner iterator,
+            // the size hint is then equal to the that of the inner iterator's.
+            (lo, hi)
+        } else {
+            // If the first `N` items are not yet yielded by the inner iterator,
+            // the first `N` elements should be counted as one window, so both bounds
+            // should subtract `N - 1`.
+            (lo.saturating_sub(N - 1), hi.map(|hi| hi.saturating_sub(N - 1)))
+        }
+    }
+}
+
+impl<T, const N: usize> Buffer<T, N> {
+    fn try_from_iter(iter: &mut impl Iterator<Item = T>) -> Option<Self> {
+        let first_half = crate::array::iter_next_chunk(iter).ok()?;
+        let buffer = [MaybeUninit::new(first_half).transpose(), MaybeUninit::uninit_array()];
+        Some(Self { buffer, start: 0 })
+    }
+
+    #[inline]
+    fn buffer_ptr(&self) -> *const MaybeUninit<T> {
+        self.buffer.as_ptr().cast()
+    }
+
+    #[inline]
+    fn buffer_mut_ptr(&mut self) -> *mut MaybeUninit<T> {
+        self.buffer.as_mut_ptr().cast()
+    }
+
+    #[inline]
+    fn as_array_ref(&self) -> &[T; N] {
+        debug_assert!(self.start + N <= 2 * N);
+
+        // SAFETY: our invariant guarantees these elements are initialized.
+        unsafe { &*self.buffer_ptr().add(self.start).cast() }
+    }
+
+    #[inline]
+    fn as_uninit_array_mut(&mut self) -> &mut MaybeUninit<[T; N]> {
+        debug_assert!(self.start + N <= 2 * N);
+
+        // SAFETY: our invariant guarantees these elements are in bounds.
+        unsafe { &mut *self.buffer_mut_ptr().add(self.start).cast() }
+    }
+
+    /// Pushes a new item `next` to the back, and pops the front-most one.
+    ///
+    /// All the elements will be shifted to the front end when pushing reaches
+    /// the back end.
+    fn push(&mut self, next: T) {
+        let buffer_mut_ptr = self.buffer_mut_ptr();
+        debug_assert!(self.start + N <= 2 * N);
+
+        let to_drop = if self.start == N {
+            // We have reached the end of our buffer and have to copy
+            // everything to the start. Example layout for N = 3.
+            //
+            //    0   1   2   3   4   5            0   1   2   3   4   5
+            //  ┌───┬───┬───┬───┬───┬───┐        ┌───┬───┬───┬───┬───┬───┐
+            //  │ - │ - │ - │ a │ b │ c │   ->   │ b │ c │ n │ - │ - │ - │
+            //  └───┴───┴───┴───┴───┴───┘        └───┴───┴───┴───┴───┴───┘
+            //                ↑                    ↑
+            //              start                start
+
+            // SAFETY: the two pointers are valid for reads/writes of N -1
+            // elements because our array's size is semantically 2 * N. The
+            // regions also don't overlap for the same reason.
+            //
+            // We leave the old elements in place. As soon as `start` is set
+            // to 0, we treat them as uninitialized and treat their copies
+            // as initialized.
+            let to_drop = unsafe {
+                ptr::copy_nonoverlapping(buffer_mut_ptr.add(self.start + 1), buffer_mut_ptr, N - 1);
+                (*buffer_mut_ptr.add(N - 1)).write(next);
+                buffer_mut_ptr.add(self.start)
+            };
+            self.start = 0;
+            to_drop
+        } else {
+            // SAFETY: `self.start` is < N as guaranteed by the invariant
+            // plus the check above. Even if the drop at the end panics,
+            // the invariant is upheld.
+            //
+            // Example layout for N = 3:
+            //
+            //    0   1   2   3   4   5            0   1   2   3   4   5
+            //  ┌───┬───┬───┬───┬───┬───┐        ┌───┬───┬───┬───┬───┬───┐
+            //  │ - │ a │ b │ c │ - │ - │   ->   │ - │ - │ b │ c │ n │ - │
+            //  └───┴───┴───┴───┴───┴───┘        └───┴───┴───┴───┴───┴───┘
+            //        ↑                                    ↑
+            //      start                                start
+            //
+            let to_drop = unsafe {
+                (*buffer_mut_ptr.add(self.start + N)).write(next);
+                buffer_mut_ptr.add(self.start)
+            };
+            self.start += 1;
+            to_drop
+        };
+
+        // SAFETY: the index is valid and this is element `a` in the
+        // diagram above and has not been dropped yet.
+        unsafe { ptr::drop_in_place(to_drop.cast::<T>()) };
+    }
+}
+
+impl<T: Clone, const N: usize> Clone for Buffer<T, N> {
+    fn clone(&self) -> Self {
+        let mut buffer = Buffer {
+            buffer: [MaybeUninit::uninit_array(), MaybeUninit::uninit_array()],
+            start: self.start,
+        };
+        buffer.as_uninit_array_mut().write(self.as_array_ref().clone());
+        buffer
+    }
+}
+
+impl<I, const N: usize> Clone for MapWindowsInner<I, N>
+where
+    I: Iterator + Clone,
+    I::Item: Clone,
+{
+    fn clone(&self) -> Self {
+        Self { iter: self.iter.clone(), buffer: self.buffer.clone() }
+    }
+}
+
+impl<T, const N: usize> Drop for Buffer<T, N> {
+    fn drop(&mut self) {
+        // SAFETY: our invariant guarantees that N elements starting from
+        // `self.start` are initialized. We drop them here.
+        unsafe {
+            let initialized_part: *mut [T] = crate::ptr::slice_from_raw_parts_mut(
+                self.buffer_mut_ptr().add(self.start).cast(),
+                N,
+            );
+            ptr::drop_in_place(initialized_part);
+        }
+    }
+}
+
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+impl<I, F, R, const N: usize> Iterator for MapWindows<I, F, N>
+where
+    I: Iterator,
+    F: FnMut(&[I::Item; N]) -> R,
+{
+    type Item = R;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let window = self.inner.next_window()?;
+        let out = (self.f)(window);
+        Some(out)
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.inner.size_hint()
+    }
+}
+
+// Note that even if the inner iterator not fused, the `MapWindows` is still fused,
+// because we don't allow "holes" in the mapping window.
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+impl<I, F, R, const N: usize> FusedIterator for MapWindows<I, F, N>
+where
+    I: Iterator,
+    F: FnMut(&[I::Item; N]) -> R,
+{
+}
+
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+impl<I, F, R, const N: usize> ExactSizeIterator for MapWindows<I, F, N>
+where
+    I: ExactSizeIterator,
+    F: FnMut(&[I::Item; N]) -> R,
+{
+}
+
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+impl<I: Iterator + fmt::Debug, F, const N: usize> fmt::Debug for MapWindows<I, F, N> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("MapWindows").field("iter", &self.inner.iter).finish()
+    }
+}
+
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+impl<I, F, const N: usize> Clone for MapWindows<I, F, N>
+where
+    I: Iterator + Clone,
+    F: Clone,
+    I::Item: Clone,
+{
+    fn clone(&self) -> Self {
+        Self { f: self.f.clone(), inner: self.inner.clone() }
+    }
+}
diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs
index 8cc2b7cec4165..6f4fa7010f446 100644
--- a/library/core/src/iter/adapters/mod.rs
+++ b/library/core/src/iter/adapters/mod.rs
@@ -16,6 +16,7 @@ mod inspect;
 mod intersperse;
 mod map;
 mod map_while;
+mod map_windows;
 mod peekable;
 mod rev;
 mod scan;
@@ -57,6 +58,9 @@ pub use self::intersperse::{Intersperse, IntersperseWith};
 #[stable(feature = "iter_map_while", since = "1.57.0")]
 pub use self::map_while::MapWhile;
 
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+pub use self::map_windows::MapWindows;
+
 #[unstable(feature = "trusted_random_access", issue = "none")]
 pub use self::zip::TrustedRandomAccess;
 
diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs
index be04dfe042e9f..ca977d1ef8240 100644
--- a/library/core/src/iter/mod.rs
+++ b/library/core/src/iter/mod.rs
@@ -440,6 +440,8 @@ pub use self::adapters::Copied;
 pub use self::adapters::Flatten;
 #[stable(feature = "iter_map_while", since = "1.57.0")]
 pub use self::adapters::MapWhile;
+#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+pub use self::adapters::MapWindows;
 #[unstable(feature = "inplace_iteration", issue = "none")]
 pub use self::adapters::SourceIter;
 #[stable(feature = "iterator_step_by", since = "1.28.0")]
diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs
index cecc120a6e28d..ac1fc26a1efac 100644
--- a/library/core/src/iter/traits/iterator.rs
+++ b/library/core/src/iter/traits/iterator.rs
@@ -10,7 +10,8 @@ use super::super::{ArrayChunks, Chain, Cloned, Copied, Cycle, Enumerate, Filter,
 use super::super::{FlatMap, Flatten};
 use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
 use super::super::{
-    Inspect, Map, MapWhile, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile,
+    Inspect, Map, MapWhile, MapWindows, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take,
+    TakeWhile,
 };
 
 fn _assert_is_object_safe(_: &dyn Iterator<Item = ()>) {}
@@ -1591,6 +1592,163 @@ pub trait Iterator {
         Flatten::new(self)
     }
 
+    /// Calls the given function `f` for each contiguous window of size `N` over
+    /// `self` and returns an iterator over the outputs of `f`. Like [`slice::windows()`],
+    /// the windows during mapping overlap as well.
+    ///
+    /// In the following example, the closure is called three times with the
+    /// arguments `&['a', 'b']`, `&['b', 'c']` and `&['c', 'd']` respectively.
+    ///
+    /// ```
+    /// #![feature(iter_map_windows)]
+    ///
+    /// let strings = "abcd".chars()
+    ///     .map_windows(|[x, y]| format!("{}+{}", x, y))
+    ///     .collect::<Vec<String>>();
+    ///
+    /// assert_eq!(strings, vec!["a+b", "b+c", "c+d"]);
+    /// ```
+    ///
+    /// Note that the const parameter `N` is usually inferred by the
+    /// destructured argument in the closure.
+    ///
+    /// The returned iterator yields 𝑘 − `N` + 1 items (where 𝑘 is the number of
+    /// items yielded by `self`). If 𝑘 is less than `N`, this method yields an
+    /// empty iterator.
+    ///
+    /// The returned iterator implements [`FusedIterator`], because once `self`
+    /// returns `None`, even if it returns a `Some(T)` again in the next iterations,
+    /// we cannot put it into a contigious array buffer, and thus the returned iterator
+    /// should be fused.
+    ///
+    /// [`slice::windows()`]: slice::windows
+    /// [`FusedIterator`]: crate::iter::FusedIterator
+    ///
+    /// # Panics
+    ///
+    /// Panics if `N` is 0. This check will most probably get changed to a
+    /// compile time error before this method gets stabilized.
+    ///
+    /// ```should_panic
+    /// #![feature(iter_map_windows)]
+    ///
+    /// let iter = std::iter::repeat(0).map_windows(|&[]| ());
+    /// ```
+    ///
+    /// # Examples
+    ///
+    /// Building the sums of neighboring numbers.
+    ///
+    /// ```
+    /// #![feature(iter_map_windows)]
+    ///
+    /// let mut it = [1, 3, 8, 1].iter().map_windows(|&[a, b]| a + b);
+    /// assert_eq!(it.next(), Some(4));  // 1 + 3
+    /// assert_eq!(it.next(), Some(11)); // 3 + 8
+    /// assert_eq!(it.next(), Some(9));  // 8 + 1
+    /// assert_eq!(it.next(), None);
+    /// ```
+    ///
+    /// Since the elements in the following example implement `Copy`, we can
+    /// just copy the array and get an iterator over the windows.
+    ///
+    /// ```
+    /// #![feature(iter_map_windows)]
+    ///
+    /// let mut it = "ferris".chars().map_windows(|w: &[_; 3]| *w);
+    /// assert_eq!(it.next(), Some(['f', 'e', 'r']));
+    /// assert_eq!(it.next(), Some(['e', 'r', 'r']));
+    /// assert_eq!(it.next(), Some(['r', 'r', 'i']));
+    /// assert_eq!(it.next(), Some(['r', 'i', 's']));
+    /// assert_eq!(it.next(), None);
+    /// ```
+    ///
+    /// You can also use this function to check the sortedness of an iterator.
+    /// For the simple case, rather use [`Iterator::is_sorted`].
+    ///
+    /// ```
+    /// #![feature(iter_map_windows)]
+    ///
+    /// let mut it = [0.5, 1.0, 3.5, 3.0, 8.5, 8.5, f32::NAN].iter()
+    ///     .map_windows(|[a, b]| a <= b);
+    ///
+    /// assert_eq!(it.next(), Some(true));  // 0.5 <= 1.0
+    /// assert_eq!(it.next(), Some(true));  // 1.0 <= 3.5
+    /// assert_eq!(it.next(), Some(false)); // 3.5 <= 3.0
+    /// assert_eq!(it.next(), Some(true));  // 3.0 <= 8.5
+    /// assert_eq!(it.next(), Some(true));  // 8.5 <= 8.5
+    /// assert_eq!(it.next(), Some(false)); // 8.5 <= NAN
+    /// assert_eq!(it.next(), None);
+    /// ```
+    ///
+    /// For non-fused iterators, they are fused after `map_windows`.
+    ///
+    /// ```
+    /// #![feature(iter_map_windows)]
+    ///
+    /// #[derive(Default)]
+    /// struct NonFusedIterator {
+    ///     state: i32,
+    /// }
+    ///
+    /// impl Iterator for NonFusedIterator {
+    ///     type Item = i32;
+    ///
+    ///     fn next(&mut self) -> Option<i32> {
+    ///         let val = self.state;
+    ///         self.state = self.state + 1;
+    ///
+    ///         // yields `0..5` first, then only even numbers since `6..`.
+    ///         if val < 5 || val % 2 == 0 {
+    ///             Some(val)
+    ///         } else {
+    ///             None
+    ///         }
+    ///     }
+    /// }
+    ///
+    ///
+    /// let mut iter = NonFusedIterator::default();
+    ///
+    /// // yields 0..5 first.
+    /// assert_eq!(iter.next(), Some(0));
+    /// assert_eq!(iter.next(), Some(1));
+    /// assert_eq!(iter.next(), Some(2));
+    /// assert_eq!(iter.next(), Some(3));
+    /// assert_eq!(iter.next(), Some(4));
+    /// // then we can see our iterator going back and forth
+    /// assert_eq!(iter.next(), None);
+    /// assert_eq!(iter.next(), Some(6));
+    /// assert_eq!(iter.next(), None);
+    /// assert_eq!(iter.next(), Some(8));
+    /// assert_eq!(iter.next(), None);
+    ///
+    /// // however, with `.map_windows()`, it is fused.
+    /// let mut iter = NonFusedIterator::default()
+    ///     .map_windows(|arr: &[_; 2]| *arr);
+    ///
+    /// assert_eq!(iter.next(), Some([0, 1]));
+    /// assert_eq!(iter.next(), Some([1, 2]));
+    /// assert_eq!(iter.next(), Some([2, 3]));
+    /// assert_eq!(iter.next(), Some([3, 4]));
+    /// assert_eq!(iter.next(), None);
+    ///
+    /// // it will always return `None` after the first time.
+    /// assert_eq!(iter.next(), None);
+    /// assert_eq!(iter.next(), None);
+    /// assert_eq!(iter.next(), None);
+    /// ```
+    #[inline]
+    #[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")]
+    #[rustc_do_not_const_check]
+    fn map_windows<F, R, const N: usize>(self, f: F) -> MapWindows<Self, F, N>
+    where
+        Self: Sized,
+        F: FnMut(&[Self::Item; N]) -> R,
+    {
+        MapWindows::new(self, f)
+    }
+
     /// Creates an iterator which ends after the first [`None`].
     ///
     /// After an iterator returns [`None`], future calls may or may not yield
diff --git a/library/core/tests/iter/adapters/map_windows.rs b/library/core/tests/iter/adapters/map_windows.rs
new file mode 100644
index 0000000000000..7fb2408f8acb7
--- /dev/null
+++ b/library/core/tests/iter/adapters/map_windows.rs
@@ -0,0 +1,283 @@
+use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+
+#[cfg(not(panic = "abort"))]
+mod drop_checks {
+    //! These tests mainly make sure the elements are correctly dropped.
+    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
+
+    #[derive(Debug)]
+    struct DropInfo {
+        dropped_twice: AtomicBool,
+        alive_count: AtomicUsize,
+    }
+
+    impl DropInfo {
+        const fn new() -> Self {
+            Self { dropped_twice: AtomicBool::new(false), alive_count: AtomicUsize::new(0) }
+        }
+
+        #[track_caller]
+        fn check(&self) {
+            assert!(!self.dropped_twice.load(SeqCst), "a value was dropped twice");
+            assert_eq!(self.alive_count.load(SeqCst), 0);
+        }
+    }
+
+    #[derive(Debug)]
+    struct DropCheck<'a> {
+        info: &'a DropInfo,
+        was_dropped: bool,
+    }
+
+    impl<'a> DropCheck<'a> {
+        fn new(info: &'a DropInfo) -> Self {
+            info.alive_count.fetch_add(1, SeqCst);
+
+            Self { info, was_dropped: false }
+        }
+    }
+
+    impl Drop for DropCheck<'_> {
+        fn drop(&mut self) {
+            if self.was_dropped {
+                self.info.dropped_twice.store(true, SeqCst);
+            }
+            self.was_dropped = true;
+
+            self.info.alive_count.fetch_sub(1, SeqCst);
+        }
+    }
+
+    fn iter(info: &DropInfo, len: usize, panic_at: usize) -> impl Iterator<Item = DropCheck<'_>> {
+        (0..len).map(move |i| {
+            if i == panic_at {
+                panic!("intended panic");
+            }
+            DropCheck::new(info)
+        })
+    }
+
+    #[track_caller]
+    fn check<const N: usize>(len: usize, panic_at: usize) {
+        check_drops(|info| {
+            iter(info, len, panic_at).map_windows(|_: &[_; N]| {}).last();
+        });
+    }
+
+    #[track_caller]
+    fn check_drops(f: impl FnOnce(&DropInfo)) {
+        let info = DropInfo::new();
+        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+            f(&info);
+        }));
+        info.check();
+    }
+
+    #[test]
+    fn no_iter_panic_n1() {
+        check::<1>(0, 100);
+        check::<1>(1, 100);
+        check::<1>(2, 100);
+        check::<1>(13, 100);
+    }
+
+    #[test]
+    fn no_iter_panic_n2() {
+        check::<2>(0, 100);
+        check::<2>(1, 100);
+        check::<2>(2, 100);
+        check::<2>(3, 100);
+        check::<2>(13, 100);
+    }
+
+    #[test]
+    fn no_iter_panic_n5() {
+        check::<5>(0, 100);
+        check::<5>(1, 100);
+        check::<5>(2, 100);
+        check::<5>(13, 100);
+        check::<5>(30, 100);
+    }
+
+    #[test]
+    fn panic_in_first_batch() {
+        check::<1>(7, 0);
+
+        check::<2>(7, 0);
+        check::<2>(7, 1);
+
+        check::<3>(7, 0);
+        check::<3>(7, 1);
+        check::<3>(7, 2);
+    }
+
+    #[test]
+    fn panic_in_middle() {
+        check::<1>(7, 1);
+        check::<1>(7, 5);
+        check::<1>(7, 6);
+
+        check::<2>(7, 2);
+        check::<2>(7, 5);
+        check::<2>(7, 6);
+
+        check::<5>(13, 5);
+        check::<5>(13, 8);
+        check::<5>(13, 12);
+    }
+
+    #[test]
+    fn len_equals_n() {
+        check::<1>(1, 100);
+        check::<1>(1, 0);
+
+        check::<2>(2, 100);
+        check::<2>(2, 0);
+        check::<2>(2, 1);
+
+        check::<5>(5, 100);
+        check::<5>(5, 0);
+        check::<5>(5, 1);
+        check::<5>(5, 4);
+    }
+}
+
+#[test]
+fn output_n1() {
+    assert_eq!("".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec![]);
+    assert_eq!("x".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec!['x']);
+    assert_eq!("abcd".chars().map_windows(|[c]| *c).collect::<Vec<_>>(), vec!['a', 'b', 'c', 'd']);
+}
+
+#[test]
+fn output_n2() {
+    assert_eq!(
+        "".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(),
+        <Vec<[char; 2]>>::new(),
+    );
+    assert_eq!("ab".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(), vec![['a', 'b']]);
+    assert_eq!(
+        "abcd".chars().map_windows(|a: &[_; 2]| *a).collect::<Vec<_>>(),
+        vec![['a', 'b'], ['b', 'c'], ['c', 'd']],
+    );
+}
+
+#[test]
+fn test_case_from_pr_82413_comment() {
+    for () in std::iter::repeat("0".to_owned()).map_windows(|_: &[_; 3]| {}).take(4) {}
+}
+
+#[test]
+#[should_panic = "array in `Iterator::map_windows` must contain more than 0 elements"]
+fn check_zero_window() {
+    let _ = std::iter::repeat(0).map_windows(|_: &[_; 0]| ());
+}
+
+#[test]
+fn test_zero_sized_type() {
+    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+    struct Data;
+    let data: Vec<_> =
+        std::iter::repeat(Data).take(10).map_windows(|arr: &[Data; 5]| *arr).collect();
+    assert_eq!(data, [[Data; 5]; 6]);
+}
+
+#[test]
+#[should_panic = "array size of `Iterator::map_windows` is too large"]
+fn test_too_large_array_size() {
+    let _ = std::iter::repeat(()).map_windows(|arr: &[(); usize::MAX]| *arr);
+}
+
+#[test]
+fn test_laziness() {
+    let counter = AtomicUsize::new(0);
+    let mut iter = (0..5)
+        .inspect(|_| {
+            counter.fetch_add(1, SeqCst);
+        })
+        .map_windows(|arr: &[i32; 2]| *arr);
+    assert_eq!(counter.load(SeqCst), 0);
+
+    assert_eq!(iter.next(), Some([0, 1]));
+    // The first iteration consumes N items (N = 2).
+    assert_eq!(counter.load(SeqCst), 2);
+
+    assert_eq!(iter.next(), Some([1, 2]));
+    assert_eq!(counter.load(SeqCst), 3);
+
+    assert_eq!(iter.next(), Some([2, 3]));
+    assert_eq!(counter.load(SeqCst), 4);
+
+    assert_eq!(iter.next(), Some([3, 4]));
+    assert_eq!(counter.load(SeqCst), 5);
+
+    assert_eq!(iter.next(), None);
+    assert_eq!(counter.load(SeqCst), 5);
+}
+
+#[test]
+fn test_size_hint() {
+    struct SizeHintCheckHelper((usize, Option<usize>));
+
+    impl Iterator for SizeHintCheckHelper {
+        type Item = i32;
+
+        fn next(&mut self) -> Option<i32> {
+            let (ref mut lo, ref mut hi) = self.0;
+            let next = (*hi != Some(0)).then_some(0);
+            *lo = lo.saturating_sub(1);
+            if let Some(hi) = hi {
+                *hi = hi.saturating_sub(1);
+            }
+            next
+        }
+
+        fn size_hint(&self) -> (usize, Option<usize>) {
+            self.0
+        }
+    }
+
+    fn check_size_hint<const N: usize>(
+        size_hint: (usize, Option<usize>),
+        mut mapped_size_hint: (usize, Option<usize>),
+    ) {
+        let mut iter = SizeHintCheckHelper(size_hint);
+        let mut mapped_iter = iter.by_ref().map_windows(|_: &[_; N]| ());
+        while mapped_iter.size_hint().0 > 0 {
+            assert_eq!(mapped_iter.size_hint(), mapped_size_hint);
+            assert!(mapped_iter.next().is_some());
+            mapped_size_hint.0 -= 1;
+            mapped_size_hint.1 = mapped_size_hint.1.map(|hi| hi.saturating_sub(1));
+        }
+    }
+
+    check_size_hint::<1>((0, None), (0, None));
+    check_size_hint::<1>((0, Some(0)), (0, Some(0)));
+    check_size_hint::<1>((0, Some(2)), (0, Some(2)));
+    check_size_hint::<1>((1, None), (1, None));
+    check_size_hint::<1>((1, Some(1)), (1, Some(1)));
+    check_size_hint::<1>((1, Some(4)), (1, Some(4)));
+    check_size_hint::<1>((5, None), (5, None));
+    check_size_hint::<1>((5, Some(5)), (5, Some(5)));
+    check_size_hint::<1>((5, Some(10)), (5, Some(10)));
+
+    check_size_hint::<2>((0, None), (0, None));
+    check_size_hint::<2>((0, Some(0)), (0, Some(0)));
+    check_size_hint::<2>((0, Some(2)), (0, Some(1)));
+    check_size_hint::<2>((1, None), (0, None));
+    check_size_hint::<2>((1, Some(1)), (0, Some(0)));
+    check_size_hint::<2>((1, Some(4)), (0, Some(3)));
+    check_size_hint::<2>((5, None), (4, None));
+    check_size_hint::<2>((5, Some(5)), (4, Some(4)));
+    check_size_hint::<2>((5, Some(10)), (4, Some(9)));
+
+    check_size_hint::<5>((0, None), (0, None));
+    check_size_hint::<5>((0, Some(0)), (0, Some(0)));
+    check_size_hint::<5>((0, Some(2)), (0, Some(0)));
+    check_size_hint::<5>((1, None), (0, None));
+    check_size_hint::<5>((1, Some(1)), (0, Some(0)));
+    check_size_hint::<5>((1, Some(4)), (0, Some(0)));
+    check_size_hint::<5>((5, None), (1, None));
+    check_size_hint::<5>((5, Some(5)), (1, Some(1)));
+    check_size_hint::<5>((5, Some(10)), (1, Some(6)));
+}
diff --git a/library/core/tests/iter/adapters/mod.rs b/library/core/tests/iter/adapters/mod.rs
index ca3463aa7f782..dedb4c0a9dd5f 100644
--- a/library/core/tests/iter/adapters/mod.rs
+++ b/library/core/tests/iter/adapters/mod.rs
@@ -13,6 +13,7 @@ mod fuse;
 mod inspect;
 mod intersperse;
 mod map;
+mod map_windows;
 mod peekable;
 mod scan;
 mod skip;
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index 897a5e9b87063..d849561bb21b8 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -110,6 +110,7 @@
 #![feature(is_ascii_octdigit)]
 #![feature(get_many_mut)]
 #![feature(offset_of)]
+#![feature(iter_map_windows)]
 #![deny(unsafe_op_in_unsafe_fn)]
 #![deny(fuzzy_provenance_casts)]