diff --git a/src/framebuffer2.h b/src/framebuffer2.h
--- a/src/framebuffer2.h
+++ b/src/framebuffer2.h
@@ -32,15 +32,20 @@ struct Range
 class FrameBuffer
 {
 public:
+    /// Returns size of the buffer.
     virtual unsigned size() const = 0;
+    /// Returns a sample from given index.
     virtual double sample(unsigned i) const = 0;
+    /// Returns minimum and maximum of the buffer values.
     virtual Range limits() const = 0;
 };
 
 /// Common base class for index and writable frame buffers
 class ResizableBuffer : public FrameBuffer
 {
-    /// Resize buffer
+    /// Resize the buffer.
+    ///
+    /// @important Resizing to same value is an error.
     virtual void resize(unsigned n) = 0;
 };
 
diff --git a/src/ringbuffer.cpp b/src/ringbuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.cpp
@@ -0,0 +1,172 @@
+/*
+  Copyright © 2017 Hasan Yavuz Özderya
+
+  This file is part of serialplot.
+
+  serialplot is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  serialplot is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with serialplot.  If not, see .
+*/
+
+#include 
+
+#include "ringbuffer.h"
+
+RingBuffer::RingBuffer(unsigned n)
+{
+    _size = n;
+    data = new double[_size]();
+    headIndex = 0;
+
+    limInvalid = false;
+    limCache = {0, 0};
+}
+
+RingBuffer::~RingBuffer()
+{
+    delete[] data;
+}
+
+unsigned RingBuffer::size() const
+{
+    return _size;
+}
+
+double RingBuffer::sample(unsigned i) const
+{
+    unsigned index = headIndex + i;
+    if (index >= _size) index -= _size;
+    return data[index];
+}
+
+Range RingBuffer::limits() const
+{
+    if (limInvalid) updateLimits();
+    return limCache;
+}
+
+void RingBuffer::resize(unsigned n)
+{
+    Q_ASSERT(n != _size);
+
+    int offset = (int) n - (int) _size;
+    if (offset == 0) return;
+
+    double* newData = new double[n];
+
+    // move data to new array
+    int fill_start = offset > 0 ? offset : 0;
+
+    for (int i = fill_start; i < int(n); i++)
+    {
+        newData[i] = sample(i - offset);
+    }
+
+    // fill the beginning of the new data
+    if (fill_start > 0)
+    {
+        for (int i = 0; i < fill_start; i++)
+        {
+            newData[i] = 0;
+        }
+    }
+
+    // data is ready, clean up and re-point
+    delete data;
+    data = newData;
+    headIndex = 0;
+    _size = n;
+
+    // invalidate bounding rectangle
+    limInvalid = true;
+}
+
+void RingBuffer::addSamples(double* samples, unsigned n)
+{
+    unsigned shift = n;
+    if (shift < _size)
+    {
+        unsigned x = _size - headIndex; // distance of `head` to end
+
+        if (shift <= x) // there is enough room at the end of array
+        {
+            for (unsigned i = 0; i < shift; i++)
+            {
+                data[i+headIndex] = samples[i];
+            }
+
+            if (shift == x) // we used all the room at the end
+            {
+                headIndex = 0;
+            }
+            else
+            {
+                headIndex += shift;
+            }
+        }
+        else // there isn't enough room
+        {
+            for (unsigned i = 0; i < x; i++) // fill the end part
+            {
+                data[i+headIndex] = samples[i];
+            }
+            for (unsigned i = 0; i < (shift-x); i++) // continue from the beginning
+            {
+                data[i] = samples[i+x];
+            }
+            headIndex = shift-x;
+        }
+    }
+    else // number of new samples equal or bigger than current size (doesn't fit)
+    {
+        int x = shift - _size;
+        for (unsigned i = 0; i < _size; i++)
+        {
+            data[i] = samples[i+x];
+        }
+        headIndex = 0;
+    }
+
+    // invalidate cache
+    limInvalid = true;
+}
+
+void RingBuffer::clear()
+{
+    for (unsigned i=0; i < _size; i++)
+    {
+        data[i] = 0.;
+    }
+
+    limCache = {0, 0};
+    limInvalid = false;
+}
+
+void RingBuffer::updateLimits() const
+{
+    limCache.start = data[0];
+    limCache.end = data[0];
+
+    for (unsigned i = 0; i < _size; i++)
+    {
+        if (data[i] > limCache.end)
+        {
+            limCache.end = data[i];
+        }
+        else if (data[i] < limCache.start)
+        {
+            limCache.start = data[i];
+        }
+    }
+
+    limInvalid = false;
+}
diff --git a/src/ringbuffer.h b/src/ringbuffer.h
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.h
@@ -0,0 +1,50 @@
+/*
+  Copyright © 2017 Hasan Yavuz Özderya
+
+  This file is part of serialplot.
+
+  serialplot is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  serialplot is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with serialplot.  If not, see .
+*/
+
+#ifndef RINGBUFFER_H
+#define RINGBUFFER_H
+
+// IMPORTANT TODO: rename to "framebuffer.h" when stream work is done.
+#include "framebuffer2.h"
+
+/// A fast buffer implementation for storing data.
+class RingBuffer : public WFrameBuffer
+{
+public:
+    RingBuffer(unsigned n);
+    ~RingBuffer();
+
+    virtual unsigned size() const;
+    virtual double sample(unsigned i) const;
+    virtual Range limits() const;
+    virtual void resize(unsigned n);
+    virtual void addSamples(double* samples, unsigned n);
+    virtual void clear();
+
+private:
+    unsigned _size;            ///< size of `data`
+    double* data;              ///< storage
+    unsigned headIndex;        ///< indicates the actual `0` index of the ring buffer
+
+    mutable bool limInvalid;   ///< Indicates that limits needs to be re-calculated
+    mutable Range limCache;    ///< Cache for limits()
+    void updateLimits() const; ///< Updates limits cache
+};
+
+#endif
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ add_executable(Test EXCLUDE_FROM_ALL
   ../src/source.cpp
   ../src/indexbuffer.cpp
   ../src/linindexbuffer.cpp
+  ../src/ringbuffer.cpp
   )
 add_test(NAME test1 COMMAND Test)
 qt5_use_modules(Test Widgets)
diff --git a/tests/test.cpp b/tests/test.cpp
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -24,6 +24,7 @@
 #include "source.h"
 #include "indexbuffer.h"
 #include "linindexbuffer.h"
+#include "ringbuffer.h"
 
 TEST_CASE("samplepack with no X", "[memory]")
 {
@@ -236,3 +237,130 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
     REQUIRE(buf.sample(0) == -5.0);
     REQUIRE(buf.sample(19) == 5.0);
 }
+
+TEST_CASE("RingBuffer sizing", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+
+    REQUIRE(buf.size() == 10);
+
+    buf.resize(5);
+    REQUIRE(buf.size() == 5);
+
+    buf.resize(15);
+    REQUIRE(buf.size() == 15);
+}
+
+TEST_CASE("RingBuffer initial values should be 0", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+
+    for (unsigned i = 0; i < 10; i++)
+    {
+        REQUIRE(buf.sample(i) == 0.);
+    }
+}
+
+TEST_CASE("RingBuffer data access", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    buf.addSamples(values, 10);
+
+    REQUIRE(buf.size() == 10);
+    for (unsigned i = 0; i < 10; i++)
+    {
+        REQUIRE(buf.sample(i) == values[i]);
+    }
+
+    buf.addSamples(values, 5);
+
+    REQUIRE(buf.size() == 10);
+    for (unsigned i = 0; i < 5; i++)
+    {
+        REQUIRE(buf.sample(i) == values[i+5]);
+    }
+    for (unsigned i = 5; i < 10; i++)
+    {
+        REQUIRE(buf.sample(i) == values[i-5]);
+    }
+}
+
+TEST_CASE("making RingBuffer bigger should keep end values", "[memory, buffer]")
+{
+    RingBuffer buf(5);
+    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    buf.addSamples(values, 5);
+    buf.resize(10);
+
+    REQUIRE(buf.size() == 10);
+    for (unsigned i = 0; i < 5; i++)
+    {
+        REQUIRE(buf.sample(i) == 0);
+    }
+    for (unsigned i = 5; i < 10; i++)
+    {
+        REQUIRE(buf.sample(i) == values[i-5]);
+    }
+}
+
+TEST_CASE("making RingBuffer smaller should keep end values", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    buf.addSamples(values, 10);
+    buf.resize(5);
+
+    REQUIRE(buf.size() == 5);
+    for (unsigned i = 0; i < 5; i++)
+    {
+        REQUIRE(buf.sample(i) == values[i+5]);
+    }
+}
+
+TEST_CASE("RingBuffer limits", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    auto lim = buf.limits();
+    REQUIRE(lim.start == 0.);
+    REQUIRE(lim.end == 0.);
+
+    buf.addSamples(values, 10);
+    lim = buf.limits();
+    REQUIRE(lim.start == 1.);
+    REQUIRE(lim.end == 10.);
+
+    buf.addSamples(&values[9], 1);
+    lim = buf.limits();
+    REQUIRE(lim.start == 2.);
+    REQUIRE(lim.end == 10.);
+
+    buf.addSamples(values, 9);
+    buf.addSamples(values, 1);
+    lim = buf.limits();
+    REQUIRE(lim.start == 1.);
+    REQUIRE(lim.end == 9.);
+}
+
+TEST_CASE("RingBuffer clear", "[memory, buffer]")
+{
+    RingBuffer buf(10);
+    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    buf.addSamples(values, 10);
+    buf.clear();
+
+    REQUIRE(buf.size() == 10);
+    for (unsigned i = 0; i < 10; i++)
+    {
+        REQUIRE(buf.sample(i) == 0.);
+    }
+    auto lim = buf.limits();
+    REQUIRE(lim.start == 0.);
+    REQUIRE(lim.end == 0.);
+}