Exported vs Unexported in Go: Key Differences and Usage
exported identifiers start with an uppercase letter and are accessible from other packages, while unexported identifiers start with a lowercase letter and are only accessible within the same package. Use exported names to share functionality publicly and unexported names to keep details private within a package.Quick Comparison
This table summarizes the main differences between exported and unexported identifiers in Go.
| Aspect | Exported | Unexported |
|---|---|---|
| Naming | Starts with uppercase letter (e.g., MyFunc) | Starts with lowercase letter (e.g., myFunc) |
| Visibility | Accessible from other packages | Accessible only within the same package |
| Use case | Public API or shared functionality | Internal implementation details |
| Access from other packages | Allowed | Not allowed |
| Convention | Indicates public use | Indicates private use |
Key Differences
In Go, whether an identifier is exported or unexported depends solely on the first letter of its name. If it starts with an uppercase letter, it is exported and can be accessed from other packages. This is how Go controls visibility without explicit keywords like public or private.
Unexported identifiers start with a lowercase letter and are only visible inside the package where they are declared. This allows you to hide implementation details and keep your package's internal workings private, exposing only what you want users to see.
Using exported identifiers is essential when you want to create a public API for your package, while unexported identifiers help maintain encapsulation and reduce accidental misuse from outside code.
Code Comparison
Here is an example showing an exported function and variable in a package.
package main import ( "fmt" ) // Exported variable (accessible outside package) var ExportedVar = "I am exported" // unexported variable (only accessible inside this package) var unexportedVar = "I am unexported" // Exported function func ExportedFunc() { fmt.Println("This is an exported function") } // unexported function func unexportedFunc() { fmt.Println("This is an unexported function") } func main() { fmt.Println(ExportedVar) // Works fmt.Println(unexportedVar) // Works inside same package ExportedFunc() // Works unexportedFunc() // Works inside same package }
Unexported Equivalent
Now imagine this code is in a different package trying to access the same identifiers.
package main import ( "fmt" "example.com/mypkg" ) func main() { fmt.Println(mypkg.ExportedVar) // Works: exported // fmt.Println(mypkg.unexportedVar) // Error: unexportedVar not accessible mypkg.ExportedFunc() // Works: exported // mypkg.unexportedFunc() // Error: unexportedFunc not accessible }
When to Use Which
Choose exported identifiers when you want to provide functionality or data that other packages should use. This is how you create a public interface for your package.
Choose unexported identifiers to hide implementation details and keep your package's internal logic private. This helps prevent misuse and makes your code easier to maintain.
In short, export only what is necessary and keep everything else unexported to maintain clean and safe code boundaries.