diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 65b9641..2c23d92 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -280,12 +280,15 @@ define([
             needs_height_reset = true;
         }
 
+        var record_output = true;
+
         if (json.output_type === 'execute_result') {
             this.append_execute_result(json);
         } else if (json.output_type === 'error') {
             this.append_error(json);
         } else if (json.output_type === 'stream') {
-            this.append_stream(json);
+            // append_stream might have merged the output with earlier stream output
+            record_output = this.append_stream(json);
         }
 
         // We must release the animation fixed height in a callback since Gecko
@@ -306,7 +309,9 @@ define([
             handle_appended();
         }
         
-        this.outputs.push(json);
+        if (record_output) {
+            this.outputs.push(json);
+        }
     };
 
 
@@ -457,20 +462,23 @@ define([
                 // latest output was in the same stream,
                 // so append directly into its pre tag
                 // escape ANSI & HTML specials:
+                last.text = utils.fixCarriageReturn(last.text + json.text);
                 var pre = this.element.find('div.'+subclass).last().find('pre');
-                var html = utils.fixCarriageReturn(
-                    pre.html() + utils.fixConsole(text));
+                var html = utils.fixConsole(last.text);
                 // The only user content injected with this HTML call is
                 // escaped by the fixConsole() method.
                 pre.html(html);
-                return;
+                // return false signals that we merged this output with the previous one,
+                // and the new output shouldn't be recorded.
+                return false;
             }
         }
 
         if (!text.replace("\r", "")) {
             // text is nothing (empty string, \r, etc.)
             // so don't append any elements, which might add undesirable space
-            return;
+            // return true to indicate the output should be recorded.
+            return true;
         }
 
         // If we got here, attach a new div
@@ -480,6 +488,7 @@ define([
             append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
         }
         this._safe_append(toinsert);
+        return true;
     };
 
 
diff --git a/IPython/html/tests/notebook/output.js b/IPython/html/tests/notebook/output.js
new file mode 100644
index 0000000..1fcfe44
--- /dev/null
+++ b/IPython/html/tests/notebook/output.js
@@ -0,0 +1,98 @@
+//
+// Various output tests
+//
+
+casper.notebook_test(function () {
+    
+    this.test_coalesced_output = function (msg, code, expected) {
+        this.then(function () {
+            this.echo("Test coalesced output: " + msg);
+        });
+        
+        this.thenEvaluate(function (code) {
+            IPython.notebook.insert_cell_at_index(0, "code");
+            var cell = IPython.notebook.get_cell(0);
+            cell.set_text(code);
+            cell.execute();
+        }, {code: code});
+        
+        this.wait_for_output(0);
+        
+        this.then(function () {
+            var results = this.evaluate(function () {
+                var cell = IPython.notebook.get_cell(0);
+                return cell.output_area.outputs;
+            });
+            this.test.assertEquals(results.length, expected.length, "correct number of outputs");
+            for (var i = 0; i < results.length; i++) {
+                var r = results[i];
+                var ex = expected[i];
+                this.test.assertEquals(r.output_type, ex.output_type, "output  " + i);
+                if (r.output_type === 'stream') {
+                    this.test.assertEquals(r.stream, ex.stream, "stream  " + i);
+                    this.test.assertEquals(r.text, ex.text, "content " + i);
+                }
+            }
+        });
+        
+    };
+    
+    this.thenEvaluate(function () {
+        IPython.notebook.insert_cell_at_index(0, "code");
+        var cell = IPython.notebook.get_cell(0);
+        cell.set_text([
+            "from __future__ import print_function",
+            "import sys",
+            "from IPython.display import display"
+            ].join("\n")
+        );
+        cell.execute();
+    });
+    
+    this.test_coalesced_output("stdout", [
+        "print(1)",
+        "sys.stdout.flush()",
+        "print(2)",
+        "sys.stdout.flush()",
+        "print(3)"
+        ].join("\n"), [{
+            output_type: "stream",
+            stream: "stdout",
+            text: "1\n2\n3\n"
+        }]
+    );
+    
+    this.test_coalesced_output("stdout+sdterr", [
+        "print(1)",
+        "sys.stdout.flush()",
+        "print(2)",
+        "print(3, file=sys.stderr)"
+        ].join("\n"), [{
+            output_type: "stream",
+            stream: "stdout",
+            text: "1\n2\n"
+        },{
+            output_type: "stream",
+            stream: "stderr",
+            text: "3\n"
+        }]
+    );
+
+    this.test_coalesced_output("display splits streams", [
+        "print(1)",
+        "sys.stdout.flush()",
+        "display(2)",
+        "print(3)"
+        ].join("\n"), [{
+            output_type: "stream",
+            stream: "stdout",
+            text: "1\n"
+        },{
+            output_type: "display_data",
+        },{
+            output_type: "stream",
+            stream: "stdout",
+            text: "3\n"
+        }]
+    );
+});
diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js
index 5eb8d5b..dce37a2 100644
--- a/IPython/html/tests/widgets/widget.js
+++ b/IPython/html/tests/widgets/widget.js
@@ -151,7 +151,7 @@ casper.notebook_test(function () {
         'display(textbox)\n' +
         'textbox.add_class("my-throttle-textbox")\n' +
         'def handle_change(name, old, new):\n' +
-        '    print(len(new))\n' +
+        '    display(len(new))\n' +
         '    time.sleep(0.5)\n' +
         'textbox.on_trait_change(handle_change, "value")\n' +
         'print(textbox.model_id)');
@@ -182,7 +182,7 @@ casper.notebook_test(function () {
         this.test.assert(outputs.length <= 5, 'Messages throttled.');
 
         // We also need to verify that the last state sent was correct.
-        var last_state = outputs[outputs.length-1].text;
-        this.test.assertEquals(last_state, "20\n", "Last state sent when throttling.");
+        var last_state = outputs[outputs.length-1]['text/plain'];
+        this.test.assertEquals(last_state, "20", "Last state sent when throttling.");
     });
 });
diff --git a/IPython/html/tests/widgets/widget_button.js b/IPython/html/tests/widgets/widget_button.js
index 75b35e3..ae0aec6 100644
--- a/IPython/html/tests/widgets/widget_button.js
+++ b/IPython/html/tests/widgets/widget_button.js
@@ -8,14 +8,14 @@ casper.notebook_test(function () {
 
     var button_index = this.append_cell(
         'button = widgets.ButtonWidget(description="Title")\n' +
-        'display(button)\n'+
+        'display(button)\n' +
         'print("Success")\n' +
         'def handle_click(sender):\n' +
-        '    print("Clicked")\n' +
+        '    display("Clicked")\n' +
         'button.on_click(handle_click)');
     this.execute_cell_then(button_index, function(index){
 
-        this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 
+        this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
             'Create button cell executed with correct output.');
 
         this.test.assert(this.cell_element_exists(index, 
@@ -37,7 +37,7 @@ casper.notebook_test(function () {
     this.wait_for_output(button_index, 1);
 
     this.then(function () {
-        this.test.assertEquals(this.get_output_cell(button_index, 1).text, 'Clicked\n', 
+        this.test.assertEquals(this.get_output_cell(button_index, 1)['text/plain'], "'Clicked'",
             'Button click event fires.');
     });
 });
\ No newline at end of file
diff --git a/docs/source/whatsnew/pr/coalesce-streams.rst b/docs/source/whatsnew/pr/coalesce-streams.rst
new file mode 100644
index 0000000..521111d
--- /dev/null
+++ b/docs/source/whatsnew/pr/coalesce-streams.rst
@@ -0,0 +1,5 @@
+- Consecutive stream (stdout/stderr) output is merged into a single output
+  in the notebook document.
+  Previously, all output messages were preserved as separate output fields in the JSON.
+  Now, the same merge is applied to the stored output as the displayed output,
+  improving document load time for notebooks with many small outputs.