Menu
Coddy logo textTech

Information Hiding in Go

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 50 of 107.

Information hiding goes beyond just making fields unexported. It's about designing your types so that external code only knows what it needs to know—nothing more.

The goal is to expose behavior through methods while keeping the internal structure completely hidden. Consider this approach:

type Counter struct {
    value    int
    maxLimit int
}

func NewCounter(limit int) *Counter {
    return &Counter{maxLimit: limit}
}

func (c *Counter) Increment() bool {
    if c.value >= c.maxLimit {
        return false
    }
    c.value++
    return true
}

func (c *Counter) Value() int {
    return c.value
}

External code doesn't know that Counter uses an int internally. It could be a float64, a slice, or something else entirely. The implementation can change without affecting any code that uses Counter—as long as the methods behave the same way.

This principle also applies to helper functions. Keep internal logic unexported:

// Exported - part of public API
func (c *Counter) Reset() {
    c.value = 0
}

// unexported - internal helper
func (c *Counter) isAtLimit() bool {
    return c.value >= c.maxLimit
}

By hiding isAtLimit(), you're free to change or remove it later. Information hiding creates a clear boundary between what your package promises to external users and how it works internally.

challenge icon

Challenge

Easy

Let's build a secure wallet system that demonstrates information hiding. You'll create a digital wallet where the internal balance tracking and transaction history are completely hidden from external code—only behavior is exposed through methods.

You'll organize your code across two files:

  • wallet.go: Create a Wallet struct that hides all its internal state. The wallet should track the current balance and a spending limit internally, but external code should never know how these are stored or represented. Expose behavior through these methods:
    • NewWallet(limit float64) *Wallet - constructor that creates a wallet with the given spending limit and zero balance
    • Deposit(amount float64) bool - adds money to the wallet, returns true if successful (amount must be positive)
    • Spend(amount float64) bool - removes money if sufficient balance exists AND the amount doesn't exceed the spending limit, returns true if successful
    • Balance() float64 - returns the current balance
    • Status() string - returns a status message in the format: Balance: $[balance] (Limit: $[limit])
    Keep any helper logic (like checking if a transaction is valid) as unexported functions or methods—these are internal implementation details that could change later.
  • main.go: Read wallet configuration and transactions from input, create a wallet, perform operations, and display results after each action.

The following inputs will be provided:

  • Line 1: Spending limit
  • Line 2: Deposit amount
  • Line 3: First spend amount
  • Line 4: Second spend amount

After creating the wallet, print its initial status. Then perform each operation and print either the updated status (if successful) or Transaction failed (if the operation was rejected). Format all dollar amounts with two decimal places.

For example, given 50, 100, 30, and 80, your output should be:

Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failed

The last transaction fails because $80 exceeds the $50 spending limit—even though there's enough balance. The wallet enforces its rules internally without exposing how it makes these decisions. External code simply calls methods and receives results, never knowing the internal implementation details.

Cheat sheet

Information hiding means designing types so external code only knows what it needs to know. Expose behavior through methods while keeping internal structure completely hidden.

Example of a type with hidden implementation:

type Counter struct {
    value    int
    maxLimit int
}

func NewCounter(limit int) *Counter {
    return &Counter{maxLimit: limit}
}

func (c *Counter) Increment() bool {
    if c.value >= c.maxLimit {
        return false
    }
    c.value++
    return true
}

func (c *Counter) Value() int {
    return c.value
}

External code doesn't know the internal representation (could be int, float64, or anything else). The implementation can change without affecting code that uses it.

Keep helper functions unexported:

// Exported - part of public API
func (c *Counter) Reset() {
    c.value = 0
}

// unexported - internal helper
func (c *Counter) isAtLimit() bool {
    return c.value >= c.maxLimit
}

Information hiding creates a clear boundary between what your package promises externally and how it works internally.

Try it yourself

package main

import (
	"fmt"
)

func main() {
	// Read inputs
	var limit float64
	var depositAmount float64
	var spend1 float64
	var spend2 float64

	fmt.Scanln(&limit)
	fmt.Scanln(&depositAmount)
	fmt.Scanln(&spend1)
	fmt.Scanln(&spend2)

	// TODO: Create a new wallet with the given spending limit

	// TODO: Print initial status

	// TODO: Perform deposit and print result (status or "Transaction failed")

	// TODO: Perform first spend and print result

	// TODO: Perform second spend and print result
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming