Last modified: Jan 23, 2026 By Alexander Williams

Go Exported vs Unexported Variables Guide

Understanding variable visibility is key in Go. It controls what parts of your code other packages can see.

This concept is called exporting. It is a core part of Go's design philosophy.

This guide will make exported and unexported variables simple. You will learn how to use them effectively.

What Are Exported and Unexported Identifiers?

In Go, an identifier is a name for a variable, function, or type. Its first letter determines its visibility.

An exported identifier starts with a capital letter. It is accessible from outside its package.

An unexported identifier starts with a lowercase letter. It is only accessible within its own package.

This rule is simple but powerful. It enforces clear APIs and encapsulation.

Why Does Go Have This Rule?

Go uses this case-sensitive rule for a few important reasons. It creates clean and safe APIs.

First, it makes a package's public interface obvious. You can see what is exported at a glance.

Second, it prevents accidental access to internal package state. This improves security and stability.

Third, it reduces naming conflicts. Different packages can have private variables with the same name.

This approach is different from languages using keywords like public or private. Go's rule is part of its syntax.

Declaring Exported Variables

To export a variable, declare it with a capital first letter at the package level. Let's look at an example.


// File: config/config.go
package config

// Exported variable. Can be used by other packages.
var ApiKey = "default-key-12345"

// Exported constant.
const MaxRetries = 3

// Unexported variable. Only for use inside this package.
var apiSecret = "super-secret"

Now, see how another package imports and uses these.


// File: main.go
package main

import (
    "fmt"
    "yourproject/config"
)

func main() {
    // This works. ApiKey is exported.
    fmt.Println("API Key:", config.ApiKey)
    fmt.Println("Max Retries:", config.MaxRetries)

    // This will NOT compile. apiSecret is unexported.
    // fmt.Println(config.apiSecret) // ERROR: cannot refer to unexported name
}

Output:
API Key: default-key-12345
Max Retries: 3

The compiler will stop you from accessing apiSecret. This protects the package's internal data.

Declaring Unexported Variables

Unexported variables are your package's private helpers. They start with a lowercase letter.

They are perfect for internal state, caches, or configuration not meant for public use.


// File: calculator/math.go
package calculator

// unexported variable for internal use only
var precision = 2

// Exported function that uses the unexported variable.
func SetPrecision(p int) {
    if p > 0 {
        precision = p
    }
}

func GetResult(value float64) float64 {
    // Internal logic using the unexported 'precision'
    // ... rounding logic based on precision ...
    return value // simplified for example
}

From another package, you can call SetPrecision but not read precision directly. This is a common pattern for controlled access.

For more on different ways to create variables, see our guide on Go Variable Declaration: var vs := Explained.

Key Rules and Common Mistakes

The rule applies to all identifiers: variables, constants, functions, types, and fields in a struct.

Remember, the rule is about the first character of the identifier name, not the first character of the file or line.

A common mistake is trying to export a variable declared inside a function. Only package-level identifiers can be exported.


package mypkg

func MyFunc() {
    // This is NOT an exported variable. It's a local variable.
    var ExportedLocal = 10 // This capital 'E' does nothing for package export.
}

Another mistake is confusing variable scope with export rules. A variable's scope (where it's declared) is separate from its export status. Learn more about scope in our article Go Variable Scope in for Loops Explained.

Also, be careful not to accidentally shadow variables, which can lead to confusing bugs. We cover this in Go: Avoid Variable Shadowing Mistakes.

Practical Example: A Simple Cache Package

Let's build a small cache package. It will show exported and unexported variables in action.


// File: cache/cache.go
package cache

import "sync"

// Exported struct. Users can create a Cache.
type Cache struct {
    // Exported field. Users can read/write Name.
    Name string

    // Unexported field. Internal map is protected.
    data map[string]string
    mu   sync.RWMutex // Unexported mutex for safety.
}

// Exported function to create a new Cache.
func NewCache(name string) *Cache {
    return &Cache{
        Name: name,
        data: make(map[string]string),
    }
}

// Exported method to store a value.
func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

// Exported method to get a value.
func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

// File: main.go
package main

import (
    "fmt"
    "yourproject/cache"
)

func main() {
    myCache := cache.NewCache("MyAppCache")
    myCache.Set("user:1", "Alice")

    value, found := myCache.Get("user:1")
    if found {
        fmt.Println("Found:", value) // Output: Found: Alice
    }

    // We can access the exported field.
    fmt.Println("Cache Name:", myCache.Name)

    // We CANNOT directly access the unexported fields.
    // fmt.Println(myCache.data) // Compiler Error
    // fmt.Println(myCache.mu)   // Compiler Error
}

Output:
Found: Alice
Cache Name: MyAppCache

This example shows a clean API. The internal data map and mu mutex are safely hidden.

Best Practices

Follow these tips to use exported and unexported variables well.

Export Sparingly: Only export what is necessary for your package's API. Keep everything else unexported.

Use Exported Getters/Setters: For internal state that needs external control, provide exported functions or methods. This is called encapsulation.

Name Clearly: Use clear names. Exported names should be descriptive for users. Unexported names can be shorter but still clear for maintainers.

Group Related Variables: Consider placing related configuration into an exported struct. This keeps your API tidy.

When deciding what to export, think about the difference between variables that might change and constants that won't. Our guide on Go Constants vs Variables: When and How to Use can help.

Conclusion

Go's exported vs unexported rule is elegant. It uses a simple naming convention to control visibility.

Capital first letter means exported. Lowercase first letter means unexported.

This system helps you build robust packages. It creates clear boundaries between public APIs and private implementation.

Always start with variables unexported. Only export them when you have a good reason. This practice leads to better, more maintainable Go code.

Master this concept. It is a fundamental part of writing idiomatic and effective Go programs.