diff --git a/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs b/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs
index 050088ccb735b3c221b45d0db255779b54924ade..9e0bf57c92be698720f1007e007c5466b9e78129 100644
--- a/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs
+++ b/src/Microsoft.AspNetCore.WebUtilities/HttpResponseStreamWriter.cs
@@ -3,7 +3,9 @@
 
 using System;
 using System.Buffers;
+using System.Diagnostics;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -150,33 +152,65 @@ namespace Microsoft.AspNetCore.WebUtilities
             }
         }
 
-        public override async Task WriteAsync(char value)
+        public override Task WriteAsync(char value)
         {
             if (_disposed)
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
 
             if (_charBufferCount == _charBufferSize)
             {
-                await FlushInternalAsync(flushEncoder: false);
+                return WriteAsyncAwaited(value);
+            }
+            else
+            {
+                // Enough room in buffer, no need to go async
+                _charBuffer[_charBufferCount] = value;
+                _charBufferCount++;
+                return Task.CompletedTask;
             }
+        }
+
+        private async Task WriteAsyncAwaited(char value)
+        {
+            Debug.Assert(_charBufferCount == _charBufferSize);
+
+            await FlushInternalAsync(flushEncoder: false);
 
             _charBuffer[_charBufferCount] = value;
             _charBufferCount++;
         }
 
-        public override async Task WriteAsync(char[] values, int index, int count)
+        public override Task WriteAsync(char[] values, int index, int count)
         {
             if (_disposed)
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
 
-            if (values == null)
+            if (values == null || count == 0)
             {
-                return;
+                return Task.CompletedTask;
+            }
+
+            var remaining = _charBufferSize - _charBufferCount;
+            if (remaining >= count)
+            {
+                // Enough room in buffer, no need to go async
+                CopyToCharBuffer(values, ref index, ref count);
+                return Task.CompletedTask;
             }
+            else
+            {
+                return WriteAsyncAwaited(values, index, count);
+            }
+        }
+
+        private async Task WriteAsyncAwaited(char[] values, int index, int count)
+        {
+            Debug.Assert(count > 0);
+            Debug.Assert(_charBufferSize - _charBufferCount > count);
 
             while (count > 0)
             {
@@ -186,22 +220,43 @@ namespace Microsoft.AspNetCore.WebUtilities
                 }
 
                 CopyToCharBuffer(values, ref index, ref count);
+                Debug.Assert(count == 0);
             }
         }
 
-        public override async Task WriteAsync(string value)
+        public override Task WriteAsync(string value)
         {
             if (_disposed)
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
 
-            if (value == null)
+            var count = value?.Length ?? 0;
+            if (count == 0)
             {
-                return;
+                return Task.CompletedTask;
             }
 
+            var remaining = _charBufferSize - _charBufferCount;
+            if (remaining >= count)
+            {
+                // Enough room in buffer, no need to go async
+                CopyToCharBuffer(value);
+                return Task.CompletedTask;
+            }
+            else
+            {
+                return WriteAsyncAwaited(value);
+            }
+        }
+
+        private async Task WriteAsyncAwaited(string value)
+        {
             var count = value.Length;
+
+            Debug.Assert(count > 0);
+            Debug.Assert(_charBufferSize - _charBufferCount < count);
+
             var index = 0;
             while (count > 0)
             {
@@ -231,7 +286,7 @@ namespace Microsoft.AspNetCore.WebUtilities
         {
             if (_disposed)
             {
-                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+                return GetObjectDisposedTask();
             }
 
             return FlushInternalAsync(flushEncoder: true);
@@ -306,6 +361,19 @@ namespace Microsoft.AspNetCore.WebUtilities
             }
         }
 
+        private void CopyToCharBuffer(string value)
+        {
+            Debug.Assert(_charBufferSize - _charBufferCount >= value.Length);
+
+            value.CopyTo(
+                sourceIndex: 0,
+                destination: _charBuffer,
+                destinationIndex: _charBufferCount,
+                count: value.Length);
+
+            _charBufferCount += value.Length;
+        }
+
         private void CopyToCharBuffer(string value, ref int index, ref int count)
         {
             var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
@@ -336,5 +404,11 @@ namespace Microsoft.AspNetCore.WebUtilities
             index += remaining;
             count -= remaining;
         }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static Task GetObjectDisposedTask()
+        {
+            return Task.FromException(new ObjectDisposedException(nameof(HttpResponseStreamWriter)));
+        }
     }
 }