Question
I am new to Go and am experimenting with a notification package.
Initially, I had code like this:
func doit(w http.ResponseWriter, r *http.Request) {
notify.Post("my_event", "Hello World!")
fmt.Fprint(w, "+OK")
}
I wanted to append a newline to Hello World!, but not inside doit, because that would be straightforward. Instead, I wanted to do it later in the handler:
func handler(w http.ResponseWriter, r *http.Request) {
myEventChan := make(chan interface{})
notify.Start("my_event", myEventChan)
data := <-myEventChan
fmt.Fprint(w, data+"\n")
}
When I ran the program, I got this compiler error:
go run lp.go
# command-line-arguments
./lp.go:15: invalid operation: data + "\n" (mismatched types interface {} and string)
After searching, I changed the code to this:
func handler(w http.ResponseWriter, r *http.Request) {
myEventChan := make(chan interface{})
notify.Start("my_event", myEventChan)
data := <-myEventChan
s := data.(string) + "\n"
fmt.Fprint(w, s)
}
Is using data.(string) the correct approach here? Is it efficient, and is there a better or safer way to handle this in Go?
Short Answer
By the end of this page, you will understand why a value of type interface{} cannot be used like a string directly in Go, how type assertions work, when they are safe, and what alternatives are commonly used in real Go code.
Concept
In Go, interface{} means “a value of any type.” It can hold a string, int, struct, or anything else. That flexibility is useful, but it also means the compiler does not know the concrete type at compile time.
So when you write:
data := <-myEventChan
fmt.Fprint(w, data+"\n")
data has the static type interface{}. Even if the actual value inside it happens to be a string, Go will not let you use string operations on it until you explicitly say, “I know this contains a string.”
That is what a type assertion does:
data.(string)
This tells Go to extract the underlying value as a string.
Why this matters:
- Go is strongly typed.
interface{}trades type safety for flexibility.- Before using the stored value as a specific type, you must recover that type.
Your updated code works because you asserted that the interface value contains a string.
However, there is an important detail:
s := data.(string)
This will if does not actually hold a string.
Mental Model
Think of interface{} as a sealed box with a label that says:
- “There is some value inside.”
- “But I am not telling you what type it is.”
If you want to treat the contents as a string, you must open the box and check.
A type assertion is like saying:
- “I believe this box contains a string.”
If you use the direct form:
data.(string)
that is like opening the box with confidence. If you are wrong, the program crashes.
If you use the safe form:
s, ok := data.(string)
that is like opening the box carefully and checking before using the value.
So the key idea is:
interface{}stores anything- string operations require specifically a string
- type assertions bridge that gap
Syntax and Examples
The basic syntax for a type assertion in Go is:
value := x.(T)
This means: treat x as type T.
Direct type assertion
var data interface{} = "Hello"
s := data.(string)
fmt.Println(s + "\n")
This works if data really contains a string.
If not, it panics.
Safe type assertion
var data interface{} = "Hello"
s, ok := data.(string)
if !ok {
fmt.Println("data is not a string")
return
}
fmt.Print(s + "\n")
This is safer because it avoids a panic.
Applied to your handler
A safer version of your code would look like this:
func handler(w http.ResponseWriter, r *http.Request) {
myEventChan := ( {})
notify.Start(, myEventChan)
data := <-myEventChan
s, ok := data.()
!ok {
http.Error(w, , http.StatusInternalServerError)
}
fmt.Fprint(w, s+)
}
Step by Step Execution
Consider this small example:
package main
import "fmt"
func main() {
ch := make(chan interface{}, 1)
ch <- "Hello World!"
data := <-ch
s, ok := data.(string)
if !ok {
fmt.Println("not a string")
return
}
fmt.Println(s)
}
Here is what happens step by step:
-
ch := make(chan interface{}, 1)- Creates a channel that can carry values of any type.
-
ch <- "Hello World!"- Sends a string into the channel.
- Although the channel type is
interface{}, the concrete value is still a string.
-
data := <-ch- Receives the value from the channel.
datahas typeinterface{}.- Inside, it currently holds the string .
Real World Use Cases
Type assertions appear in real Go programs whenever code works with generic values.
Common use cases
-
HTTP and JSON processing
- When decoding unknown JSON into
map[string]interface{} - You often need assertions like
name, ok := m["name"].(string)
- When decoding unknown JSON into
-
Channels carrying mixed event data
- Event systems sometimes use
chan interface{}to allow different payload types - Handlers then assert the expected type before processing
- Event systems sometimes use
-
Error handling and custom error types
- You may receive an
errorand assert a more specific custom error type
- You may receive an
-
Generic containers or plugin systems
- Some libraries expose values as
interface{}to stay flexible
- Some libraries expose values as
-
Context values
- Values stored with
context.WithValueare retrieved asinterface{}and usually need type assertions
- Values stored with
In your case, the event system sends data through a channel of interface{} values. That means the receiver must decide what concrete type it expects.
Real Codebase Usage
In real Go codebases, developers usually try to reduce unnecessary use of interface{} because it pushes type checks to runtime.
Common patterns
1. Safe assertions with guard clauses
s, ok := data.(string)
if !ok {
return fmt.Errorf("expected string payload")
}
This is a common pattern because it fails early and clearly.
2. Use the most specific type possible
If you control the channel type, prefer this:
myEventChan := make(chan string)
instead of:
myEventChan := make(chan interface{})
That removes the need for assertions completely.
3. Use fmt.Fprintln for line-based output
Instead of manually appending "\n":
fmt.Fprintln(w, s)
This is clearer and idiomatic.
4. Type switches when multiple payload types are allowed
Common Mistakes
1. Treating interface{} like the concrete value inside it
Broken code:
data := <-myEventChan
fmt.Fprint(w, data+"\n")
Why it fails:
dataisinterface{}+ "\n"requires a string
Fix:
s, ok := data.(string)
if !ok {
http.Error(w, "not a string", http.StatusInternalServerError)
return
}
fmt.Fprintln(w, s)
2. Using a direct assertion when the type is uncertain
Broken code:
s := data.(string)
Why it is risky:
- If
datais not a string, the program panics.
Safer version:
s, ok := data.(string)
if !ok {
// handle error
}
3. Using when a concrete type would do
Comparisons
| Concept | What it does | Safe? | When to use |
|---|---|---|---|
data.(string) | Extracts string from interface | No, panics if wrong | When you are completely sure of the type |
s, ok := data.(string) | Extracts string and reports success | Yes | Preferred when input may vary |
fmt.Sprint(data) | Converts any value to a string representation | Yes | When you only need printable output |
chan interface{} | Channel can hold any type | Flexible, less safe | When multiple payload types are truly needed |
chan string |
Cheat Sheet
// Direct type assertion
s := data.(string) // panics if data is not a string
// Safe type assertion
s, ok := data.(string)
if !ok {
// handle wrong type
}
// Better output with newline
fmt.Fprintln(w, s)
Rules to remember
interface{}can hold any type.- You cannot use string operations on an
interface{}directly. - Use a type assertion to extract the concrete type.
- Prefer the
value, ok := ...form when the type may be uncertain. - If you control the API, prefer concrete types over
interface{}. - Use
fmt.Fprintlninstead offmt.Fprint(... + "\n")when appropriate.
Good handler pattern
func handler(w http.ResponseWriter, r *http.Request) {
myEventChan := make(chan interface{})
notify.Start("my_event", myEventChan)
data := <-myEventChan
s, ok := data.(string)
if !ok {
http.Error(w, , http.StatusInternalServerError)
}
fmt.Fprintln(w, s)
}
FAQ
Why can't I concatenate interface{} and string in Go?
Because interface{} is not itself a string. It may contain a string, but Go requires you to extract the concrete type first.
Is data.(string) the correct fix?
Yes, if the value really is a string. But the safer form is s, ok := data.(string) to avoid a panic.
What happens if the assertion fails?
With data.(string), your program panics. With s, ok := data.(string), ok becomes false and you can handle it safely.
Is type assertion efficient in Go?
Yes, it is generally fine for normal application code. The bigger concern is correctness and safety, not performance.
Should I use interface{} channels?
Only if you truly need to send different types. If all values are strings, use chan string instead.
Can I just use fmt.Fprint(w, data)?
Yes, if you only want to print the value. But that does not guarantee the payload is a string.
What's more idiomatic than adding manually?
Mini Project
Description
Build a small HTTP server with two endpoints: one endpoint publishes a message into a channel as interface{}, and another endpoint reads that value, safely checks whether it is a string, and writes it back with a newline. This demonstrates type assertions, safe error handling, and idiomatic output in Go.
Goal
Create a Go web server that safely reads a string from an interface{} value and returns it in an HTTP response.
Requirements
- Create an HTTP server with two endpoints:
/sendand/read - Store sent values in a channel of type
interface{} - In
/read, use a safe type assertion to check for a string - Return an HTTP error if the value is not a string
- Write successful output with a newline
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.