If you need a golang io reader example for in-memory data, the usual pattern is to turn a []byte (sometimes called a byte array in other languages) into something that implements io.Reader so helpers like io.Copy, io.ReadAll, or parsers that accept a reader work unchanged. For golang bytes to reader or golang byte to io.Reader, use bytes.NewReader; for text in a string, strings.NewReader is the usual choice. The reverse—golang io reader from bytes in the sense of “I have a reader and want a []byte”—is typically io.ReadAll or reading into a bytes.Buffer.
Tested with Go 1.24 on Linux.
Byte slice, bytes.Reader, and io.Reader
bytes.NewReader: golang bytes reader from []byte
bytes.NewReader(b []byte) returns a *bytes.Reader, a small golang bytes reader type backed by your slice. It implements io.Reader and also supports Seek, which is handy when the same bytes are parsed more than once. The reader does not copy the slice contents into a new allocation; it reads from the slice you passed in (so avoid mutating that slice while something else is still reading unless you intend that sharing).
Run the program: it should print the first 10 bytes as text (this is so).
package main
import (
"bytes"
"fmt"
"io"
"log"
)
func main() {
testData := []byte("this is some random data in a small example!")
reader := bytes.NewReader(testData)
buf := make([]byte, 10)
if _, err := io.ReadFull(reader, buf); err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
}String to reader (strings.NewReader)
If the data starts as a string, the same golang byte reader style APIs apply: anything that takes io.Reader accepts strings.NewReader without you converting the string to []byte first.
package main
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("hello from strings.Reader")
out, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
}You should see hello from strings.Reader printed.
Fixed-size array: slice the array, then NewReader
For a [N]byte array, slice to []byte and use the same helper:
package main
import (
"bytes"
"fmt"
"io"
"log"
)
func main() {
var arr [4]byte{'a', 'b', 'c', 'd'}
r := bytes.NewReader(arr[:])
b, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}You should see abcd.
From io.Reader back to []byte or string
Prefer io.ReadAll for “read everything”
For golang io reader from bytes in reverse (reader → contiguous bytes), io.ReadAll(r) is the most direct API since Go 1.16: one call returns a new []byte or an error.
package main
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("this is some random data in a small example!")
data, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data[:10]))
}You should see this is so.
bytes.Buffer with ReadFrom (streaming into a buffer)
bytes.Buffer is useful when you read in chunks, write into the buffer from multiple sources, or already need a growable byte sink. ReadFrom copies from the reader until EOF.
package main
import (
"bytes"
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("this is some random data in a small example!")
var buf bytes.Buffer
if _, err := buf.ReadFrom(r); err != nil {
log.Fatal(err)
}
data := buf.Bytes()
fmt.Println(string(data[:10]))
}Same first-line outcome as the io.ReadAll example: this is so.
Reader to string (Buffer.String or ReadAll + convert)
Either read into a buffer and call String, or use string(io.ReadAll(r)) after checking the error. strings.Builder with io.Copy is a good fit when you are assembling text and want to minimize copying from other writers:
package main
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("this is some random data in a small example!")
var b strings.Builder
if _, err := io.Copy(&b, r); err != nil {
log.Fatal(err)
}
fmt.Println(b.String())
}You should see the full sentence printed on one line.
Choosing helpers and io.ReadCloser
- Use
bytes.NewReaderwhen you have a[]byteand only need read and optional seek. - Use
strings.NewReaderwhen you have astring. - Use
io.ReadAllwhen you want the whole stream as[]byte. - Use
bytes.Bufferwhen you need a mutable buffer or incremental reads/writes. - If a function requires
io.ReadCloserbut your data is memory-only, wrap withio.NopCloser(bytes.NewReader(b))soClosesucceeds without doing extra work.
For background on why io.Reader shows up everywhere, see interfaces in Go and the language tour’s reader examples.
Summary
This article is a practical golang io reader example path for in-memory data: bytes.NewReader gives an io.Reader from a byte slice (or from arr[:] when you started from a byte array), strings.NewReader does the same for strings, and io.ReadAll or bytes.Buffer.ReadFrom bring you back from a reader to []byte (or Buffer.String / strings.Builder for text). Pick bytes.Reader when you want a read-only, seekable view of existing bytes; pick Buffer or ReadAll when you are draining an arbitrary stream.

