Sliding Window in C#

close upSliding Window in C#close up

Posted: July 27, 2025

Last Updated: July 27, 2025

Sliding Window in C#

First, what do I mean by a sliding window? I just mean a structure that contains some fixed-length array of data that can easily be updated by cycling new values in and pushing the oldest values out.

Now, I like sliding windows. There’s just something about them that makes me happy. Are you trying to plot the N most recent datapoints in a moving profiling/timing chart? Sliding window could be good. Are you trying to maintain a real-time running average over a certain number of datapoints? Sliding window could be good again. There are lots of ways you can use a little array.

If you’re like me, and just want code, here it is for a basic sliding window and another variant window that tracks additional statistics about the window values:

SlidingWindow<T>:

// * * * * *
// Original Author: Orcsune
// Date: July 27, 2025
// License: MIT License
// * * * * *

namespace Orcsune.Core.Data {
    /// <summary>
    /// SlidingWindow class that allows new values to
    /// cycle into a small array, pushing out the
    /// oldest values as space runs out. 
    /// </summary>
    /// <typeparam name="T">The type of data contained in the window.</typeparam>
    public class SlidingWindow<T>
    {
        public int windowSize => data.Length;
        public T[] data;
        public int counter;

        public SlidingWindow(int windowSize)
        {
            ChangeWindowSize(windowSize);
        }
        
        /// <summary>
        /// Cycle a new value into the window, replacing
        /// whatever oldest value previously occupied the
        /// index.
        /// </summary>
        /// <param name="value">Incoming data</param>
        /// <returns>Replaced value</returns>
        public virtual T LogValue(T value)
        {
            T old = data[counter];
            data[counter] = value;
            counter = (counter + 1) % windowSize;
            return old;
        }

        /// <summary>
        /// Return an array of the window values in order
        /// from oldest to newest.
        /// </summary>
        /// <returns>Array of window data.</returns>
        public T[] OrderedValues()
        {
            T[] values = new T[windowSize];
            int tc = 0;
            for (int i = counter; i < windowSize; i++)
            {
                values[tc] = data[i];
                tc += 1;
            }
            for (int i = 0; i < counter; i++)
            {
                values[tc] = data[i];
                tc += 1;
            }
            return values;
        }

        /// <summary>
        /// Change the size of the window. This also
        /// resets/erases the current data in the window.
        /// </summary>
        /// <param name="newSize">New window size</param>
        public virtual void ChangeWindowSize(int newSize)
        {
            if (newSize < 2) { return; }
            counter = 0;
            data = new T[newSize];
        }
    }
}

ComparableSlidingWindow:

// * * * * *
// Original Author: Orcsune
// Date: July 27, 2025
// License: MIT License
// * * * * *

using System;

namespace Orcsune.Core.Data {
    /// <summary>
    /// Specialized SlidingWindow that tracks min and max
    /// values within the current window data using the
    /// IComparable interface enforced on the window type.
    /// </summary>
    /// <typeparam name="T">IComparable</typeparam>
    public class ComparableSlidingWindow<T> : SlidingWindow<T> where T : IComparable
    {
        public T maxValue { get; private set; }
        int maxValueIndex;
        public T minValue { get; private set; }
        int minValueIndex;

        T defaultMinValue;
        T defaultMaxValue;

        /// <summary>
        /// Constructor. The default max and min values are those
        /// that should be used to compare against other values
        /// when the old max/min value is cycled out of the window.
        /// Example: if typeof(T) == float, then default min and max
        /// could be float.MaxValue and float.MinValue respectively to
        /// make sure that the new value coming into the window always
        /// "wins" the comparison.
        /// </summary>
        /// <param name="windowSize">Window size</param>
        /// <param name="defaultMinValue">Default Min</param>
        /// <param name="defaultMaxValue">Default Max</param>
        public ComparableSlidingWindow(int windowSize, T defaultMinValue, T defaultMaxValue) : base(windowSize)
        {
            this.defaultMinValue = defaultMinValue;
            this.defaultMaxValue = defaultMaxValue;
        }

        /// <summary>
        /// Cycle a new value into the window, replacing the
        /// oldest one if full. Also performs incremental
        /// update to recalculate the current max and min
        /// values within the current window.
        /// </summary>
        /// <param name="value">Incoming data</param>
        /// <returns>Replaced value</returns>
        public override T LogValue(T value)
        {
            T old = data[counter];
            data[counter] = value;
            // If we are replacing the old maximum value,
            // then we must re-iterate the window to find
            // the new max value.
            if (maxValueIndex == counter)
            {
                maxValue = defaultMaxValue;
                for (int i = 0; i < data.Length; i++)
                {
                    if (data[i].CompareTo(maxValue) > 0)
                    {
                        maxValue = data[i]; maxValueIndex = i;
                    }
                }
            }
            else
            {
                // Otherwise, we just have to check whether the
                // new value is larger than the current max.
                if (data[counter].CompareTo(maxValue) > 0)
                {
                    maxValue = data[counter];
                    maxValueIndex = counter;
                }
            }
            // If we are replacing the old minimum value,
            // then we must re-iterate the window to find
            // the new min value.
            if (minValueIndex == counter)
            {
                minValue = defaultMinValue;
                for (int i = 0; i < data.Length; i++)
                {
                    if (data[i].CompareTo(minValue) < 0)
                    {
                        minValue = data[i]; minValueIndex = i;
                    }
                }
            }
            else
            {
                // Otherwise, we just have to check whether the
                // new value is less than the current min.
                if (data[counter].CompareTo(minValue) < 0)
                {
                    minValue = data[counter];
                    minValueIndex = counter;
                }
            }
            counter = (counter + 1) % windowSize;
            return old;
        }

        /// <summary>
        /// Change the size of the window. This also
        /// resets/erases the current data in the window.
        /// </summary>
        /// <param name="newSize">New window size</param>
        public override void ChangeWindowSize(int newSize)
        {
            if (newSize < 2) { return; }
            base.ChangeWindowSize(newSize);
            minValue = defaultMinValue;
            maxValue = defaultMaxValue;
            minValueIndex = 0;
            maxValueIndex = 0;
        }
    }
}

Example Usage:


using System;
using Orcsune.Core.Data;

class Program {
    static void Main(String[] args) {
        // New window of size 10
        ComparableSlidingWindow<double> fpsWindow = new ComparableSlidingWindow<double>(10, double.MaxValue, double.MinValue);
        // Example data points (15 values)
        double[] fpsData = new double[] {
            60, 55, 54, 57, 58,  58, 59, 59, 59, 59,  42, 57, 58, 59, 59
        };
        // Fill our sliding window
        foreach (double fps in fpsData) {
            fpsWindow.LogValue(fps);
        }
        // Calculate and print statistics
        double avgFPS = 0;
        foreach (double fps in fpsWindow.data) {
            avgFPS += fps;
        }
        avgFPS /= fpsWindow.windowSize;
        Console.WriteLine($"FPS Statistics for the past {fpsWindow.windowSize} records:");
        Console.WriteLine($"\tMin FPS: {fpsWindow.minValue}");
        Console.WriteLine($"\tMax FPS: {fpsWindow.maxValue}");
        Console.WriteLine($"\tAvg FPS: {avgFPS}");
        // FPS Statistics for the past 10 records:
        //      Min FPS: 42
        //      Max FPS: 59
        //      Avg FPS: 56.9
    }
}

Other Windows:

There are plenty of things you could add to make these sliding windows better. For example, newer C# versions seem to allow you to specify numeric interfaces, which could allow for additional metrics to be tracked incrementally within a window (such as the sum or average value, for example). You could even try making your own specialized sliding windows to accommodate whatever funky data types you make.


namespace Orcsune.Core.Data {
    /// <summary>
    /// Specialized SlidingWindow that tracks numerical
    /// statistics from the window values.
    /// </summary>
    public class NumericSlidingWindow : ComparableSlidingWindow<double>
    {
        public double sum { get; private set; }
        public double average { get; private set; }

        public NumericSlidingWindow(int windowSize, double defaultMinValue, double defaultMaxValue)
            : base(windowSize, defaultMinValue, defaultMaxValue)
        {
            sum = 0;
            average = 0;
        }

        public override double LogValue(double value)
        {
            // Track the min/max value of window
            double old = base.LogValue(value);
            // Alter other statistics
            sum -= old;
            sum += value;
            average = sum / windowSize;
            return old;
        }
        
        public override void ChangeWindowSize(int newSize)
        {
            if (newSize < 2) { return; }
            base.ChangeWindowSize(newSize);
            sum = 0;
            average = 0;
        }
    }
}

Implementation:

The implementation is pretty self-explanatory (to me) given the code. Essentially, the SlidingWindow consists of a set-length array to hold logged values, and a counter to keep track of the next index to be replaced. When logging a new value, the value at counter is replaced with the new value, and then counter is incremented. When providing the ordered values, it simply copies values into a new array starting at counter and then looping through all the original data back until it reaches counter.

Video Demos:

Here is a quick example of me using SlidingWindow to track and display various timing values in a debug menu over time. Each line is built up from data points contained in SlidingWindows. Since I don’t really need to keep track of all the collected data for the entire session and I want window calculations to be as minimal as possible, this is the perfect use case: