diff --git a/src/reducers/undo.js b/src/reducers/undo.js
index 2a669383..ce16c108 100644
--- a/src/reducers/undo.js
+++ b/src/reducers/undo.js
@@ -4,6 +4,7 @@ const UNDO = 'scratch-paint/undo/UNDO';
 const REDO = 'scratch-paint/undo/REDO';
 const SNAPSHOT = 'scratch-paint/undo/SNAPSHOT';
 const CLEAR = 'scratch-paint/undo/CLEAR';
+const MAX_STACK_SIZE = 100;
 const initialState = {
     stack: [],
     pointer: -1
@@ -35,6 +36,14 @@ const reducer = function (state, action) {
             log.warn(`Couldn't create undo snapshot, no data provided`);
             return state;
         }
+        // Overflowed or about to overflow
+        if (state.pointer >= MAX_STACK_SIZE - 1) {
+            return {
+                // Make a stack of size MAX_STACK_SIZE, cutting off the oldest snapshots.
+                stack: state.stack.slice(state.pointer - MAX_STACK_SIZE + 2, state.pointer + 1).concat(action.snapshot),
+                pointer: MAX_STACK_SIZE - 1
+            };
+        }
         return {
             // Performing an action clears the redo stack
             stack: state.stack.slice(0, state.pointer + 1).concat(action.snapshot),
@@ -75,5 +84,6 @@ export {
     undo,
     redo,
     undoSnapshot,
-    clearUndoState
+    clearUndoState,
+    MAX_STACK_SIZE
 };
diff --git a/test/unit/undo-reducer.test.js b/test/unit/undo-reducer.test.js
index 6201d9e3..e5332e38 100644
--- a/test/unit/undo-reducer.test.js
+++ b/test/unit/undo-reducer.test.js
@@ -1,6 +1,6 @@
 /* eslint-env jest */
 import undoReducer from '../../src/reducers/undo';
-import {undoSnapshot, undo, redo, clearUndoState} from '../../src/reducers/undo';
+import {undoSnapshot, undo, redo, clearUndoState, MAX_STACK_SIZE} from '../../src/reducers/undo';
 
 test('initialState', () => {
     let defaultState;
@@ -139,3 +139,75 @@ test('undoSnapshotCantRedo', () => {
     expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]);
     expect(newReduxState.stack[1]).toEqual(state3);
 });
+
+test('snapshotAtMaxStackSize', () => {
+    let defaultState;
+    const getState = function (num) {
+        return {state: num};
+    };
+    // Push MAX_STACK_SIZE states
+    let num = 1;
+    let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
+    for (num = 2; num <= MAX_STACK_SIZE; num++) {
+        reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
+    }
+
+    expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 1);
+    expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
+    expect(reduxState.stack[0].state).toEqual(1);
+
+    // Push one more
+    reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
+
+    // Stack size stays the same
+    expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 1);
+    expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
+    expect(reduxState.stack[0].state).toEqual(2); // State 1 was cut off
+    expect(reduxState.stack[MAX_STACK_SIZE - 1].state).toEqual(MAX_STACK_SIZE + 1); // Newest added state is at end
+});
+
+test('undoRedoAtMaxStackSize', () => {
+    let defaultState;
+    const getState = function (num) {
+        return {state: num};
+    };
+    // Push MAX_STACK_SIZE states
+    let num = 1;
+    let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
+    for (num = 2; num <= MAX_STACK_SIZE; num++) {
+        reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
+    }
+
+    // Undo twice and redo
+    reduxState = undoReducer(reduxState /* state */, undo() /* action */);
+    reduxState = undoReducer(reduxState /* state */, undo() /* action */);
+    reduxState = undoReducer(reduxState /* state */, redo() /* action */);
+
+    expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 2);
+    expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
+    expect(reduxState.stack[0].state).toEqual(1);
+});
+
+test('undoSnapshotAtMaxStackSize', () => {
+    let defaultState;
+    const getState = function (num) {
+        return {state: num};
+    };
+    // Push MAX_STACK_SIZE states
+    let num = 1;
+    let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
+    for (num = 2; num <= MAX_STACK_SIZE; num++) {
+        reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
+    }
+
+    // Undo twice and then take a snapshot
+    reduxState = undoReducer(reduxState /* state */, undo() /* action */);
+    reduxState = undoReducer(reduxState /* state */, undo() /* action */);
+    reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
+
+    expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 2);
+    expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE - 1);
+    expect(reduxState.stack[0].state).toEqual(1);
+    expect(reduxState.stack[MAX_STACK_SIZE - 2].state).toEqual(MAX_STACK_SIZE + 1); // Newest added state is at end
+    expect(reduxState.stack[MAX_STACK_SIZE - 3].state).toEqual(MAX_STACK_SIZE - 2); // Old redo state is gone
+});