Sliding Window in C#
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:
