Avatar
Hi, I'm Celso, a passionate Brazilian software engineer who thrives on creating and exploring innovative solutions. I specialize in web engineering and work at Globo, Brazil's leading television company, where I'm shaping the future of their CMS with a cutting-edge serverless platform. Every challenge is an opportunity to learn and push boundaries, and I’m constantly inspired to build systems that make a real impact.

Golang series: Chapter Four - Connstants in Go!

Constants in Go: A Modern Guide

Constants are fundamental building blocks in Go programming that represent fixed values which remain unchanged throughout the application lifecycle. In this comprehensive guide, we’ll explore how constants work in Go, with updated examples and practical approaches for everyday coding.

What Are Constants in Go?

Constants in Go represent fixed values that won’t change during program execution, such as:

  • Numbers: 95, 67.89
  • Strings: "I love Go"
  • Boolean values: true, false

Constants provide immutability guarantees and help prevent accidental value modifications, making your code more reliable and maintainable.

Declaring Constants

The const keyword is used to declare constants in Go. Let’s see this in action:

package main

import (
	"fmt"
)

func main() {
	const a = 50
	fmt.Println("The value of constant a:", a)
	
	// This would cause a compile-time error:
	// a = 100  // Cannot reassign constants
}

When run, this program outputs:

The value of constant a: 50

Group Declaration Syntax

Go offers a convenient syntax for declaring multiple constants in a single block:

package main

import (
	"fmt"
)

func main() {
	const (
		apiTimeout  = 30
		requestPath = "/api/v2/users"
		isEnabled   = true
	)

	fmt.Println("API Timeout:", apiTimeout)
	fmt.Println("Request Path:", requestPath)
	fmt.Println("Feature Enabled:", isEnabled)
}

This cleaner approach is commonly used in production Go code, especially for related constants.

Constants vs Variables

The key distinction with constants is that they cannot be reassigned after declaration. This code won’t compile:

package main

func main() {
	const maxRetries = 5
	
	// This will fail with: cannot assign to maxRetries
	// maxRetries = 10
	
	// Variables can be reassigned
	var currentRetries = 0
	currentRetries = 1 // This works fine
}

Compile-Time Evaluation

An important property of constants is that their values must be determinable at compile time:

package main

import (
	"fmt"
	"math"
	"time"
)

func main() {
	// Variables can be assigned runtime values
	var dynamicValue = math.Sqrt(16)
	fmt.Println("Dynamic value:", dynamicValue)
	
	// This works because 2*8 can be evaluated at compile time
	const compiledValue = 2 * 8
	fmt.Println("Compiled value:", compiledValue)
	
	// This won't compile:
	// const invalidConst = math.Sqrt(16)  // Error: math.Sqrt(16) is not constant
	// const currentTime = time.Now()      // Error: time.Now() is not constant
}

Typed vs Untyped Constants

Go constants can be either typed or untyped, which affects how they interact with variables:

package main

import (
	"fmt"
)

func main() {
	// Untyped constant (no explicit type)
	const untyped = "Hello, Go!"
	
	// Typed constant (explicitly specified type)
	const typed string = "Hello, Go!"
	
	// Untyped constants can be used with any compatible type
	var s1 string = untyped       // Works fine
	var s2 string = typed         // Works fine
	
	// Create a custom string type
	type customString string
	
	// This works because untyped constants are flexible
	var cs1 customString = untyped
	
	// This would fail:
	// var cs2 customString = typed  // Cannot use typed (type string) as customString
	
	fmt.Printf("s1: %v (type %T)\n", s1, s1)
	fmt.Printf("s2: %v (type %T)\n", s2, s2)
	fmt.Printf("cs1: %v (type %T)\n", cs1, cs1)
}

Type Conversion with Constants

When working with different types and constants, you may need explicit conversion:

package main

import "fmt"

func main() {
	// Two different string types
	var defaultName = "Alex"            // Type: string
	type customName string
	var userName customName = "Taylor"  // Type: customName
	
	// This would fail:
	// userName = defaultName  // Cannot use defaultName (type string) as customName
	
	// With explicit conversion it works
	userName = customName(defaultName)
	fmt.Println("Username after conversion:", userName)
	
	// Numeric example
	const ratio = 2.5  // Untyped constant
	var intRatio int = int(ratio)
	var floatRatio float64 = ratio
	
	fmt.Printf("Original ratio: %v\n", ratio)
	fmt.Printf("Integer ratio: %v (type %T)\n", intRatio, intRatio)
	fmt.Printf("Float ratio: %v (type %T)\n", floatRatio, floatRatio)
}

Numeric Constants and Type Flexibility

Numeric constants in Go have remarkable flexibility, allowing them to be used with various numeric types:

package main

import (
	"fmt"
)

func main() {
	const magic = 42  // Untyped numeric constant
	
	// This single constant can be used with various numeric types
	var i int = magic
	var i8 int8 = magic        // Works if value fits in int8
	var f float64 = magic
	var c complex128 = magic
	
	fmt.Printf("int: %v\n", i)
	fmt.Printf("int8: %v\n", i8)
	fmt.Printf("float64: %v\n", f)
	fmt.Printf("complex128: %v\n", c)
	
	// Numeric constant expressions are also flexible
	const result = 5.0 / 3  // Division of untyped constants
	fmt.Printf("5.0/3 = %v (type %T)\n", result, result)
	
	// Constants in expressions
	var mix = 1 + result  // Integer + floating point constant
	fmt.Printf("1 + (5.0/3) = %v (type %T)\n", mix, mix)
}

Constants in Real-World Applications

Constants are incredibly useful in production Go code for:

package main

import (
	"fmt"
	"time"
)

const (
	// API configuration
	baseURL           = "https://api.example.com/v2"
	requestTimeout    = 30 // seconds
	maxRetries        = 3
	
	// HTTP status codes
	statusOK          = 200
	statusBadRequest  = 400
	statusServerError = 500
	
	// Feature flags
	enableCaching     = true
	enableMetrics     = true
)

func main() {
	fmt.Println("Starting API client with configuration:")
	fmt.Printf("- Base URL: %s\n", baseURL)
	fmt.Printf("- Timeout: %d seconds\n", requestTimeout)
	fmt.Printf("- Max retries: %d\n", maxRetries)
	
	// Simulating an API call
	status := simulateAPICall()
	
	// Using constants for comparison
	switch status {
	case statusOK:
		fmt.Println("Request successful!")
	case statusBadRequest:
		fmt.Println("Invalid request parameters")
	case statusServerError:
		fmt.Println("Server error occurred")
	default:
		fmt.Printf("Unexpected status code: %d\n", status)
	}
}

func simulateAPICall() int {
	fmt.Println("Sending request to:", baseURL+"/users")
	
	// Simulate processing time
	time.Sleep(500 * time.Millisecond)
	
	return statusOK
}

Boolean Constants

Boolean constants in Go are straightforward but still follow the same rules as other constant types:

package main

import "fmt"

func main() {
	const (
		debugMode = true
		isProd    = false
	)
	
	// Using boolean constants in conditionals
	if debugMode {
		fmt.Println("Debug mode is enabled")
	}
	
	// Custom boolean type
	type featureFlag bool
	
	// Untyped boolean constants can be assigned to custom boolean types
	var caching featureFlag = debugMode
	fmt.Printf("Caching enabled: %v (type %T)\n", caching, caching)
	
	// This would fail:
	// var boolVar bool = caching // Cannot use caching (type featureFlag) as bool
}

String Constants

String constants are one of the most common types you’ll work with:

package main

import "fmt"

func main() {
	const (
		appName    = "GoApp"
		appVersion = "v1.2.3"
		copyright  = "© 2025 Go Developers"
	)
	
	// String constants can be concatenated at compile time
	const fullName = appName + " " + appVersion
	
	fmt.Println(fullName)
	fmt.Println(copyright)
	
	// Raw string literals preserve formatting (including newlines)
	const configTemplate = `{
  "app": "%s",
  "version": "%s",
  "debug": true
}`
	
	fmt.Printf(configTemplate, appName, appVersion)
}

Best Practices for Using Constants in Go

  1. Use constants for values that shouldn’t change, like configuration parameters, magic numbers, and status codes
  2. Group related constants using the block declaration syntax for better organization
  3. Prefer untyped constants unless you need explicit type enforcement
  4. Use constants to document intent - a constant named maxRetries is more meaningful than a literal 3
  5. Consider using iota for creating sequences of related constants (covered in a separate guide)

Conclusion

Constants in Go provide immutability and type flexibility that help create more reliable and maintainable code. By understanding the nuances of typed versus untyped constants and their compile-time requirements, you can leverage them effectively in your Go applications.

Whether you’re building a simple utility or a complex service, constants play a vital role in making your code more readable and robust. Take advantage of Go’s constant declaration syntax and type system to create cleaner, more expressive code.

all tags