Scoping
Scope is the region of the program where a variable, constant, function, or type is accessible.
In Go, scope also impacts whether a value is stored on the stack (short-lived, inside function scope) or on the heap (long-lived, escaped variables).
Local Scope (Block Scope)
- Variables declared inside a block
{ ... }exist only within that block. - Stored on the stack by default.
- Destroyed (memory reclaimed) when the function exits, unless the variable escapes (referenced outside).
package main
import "fmt"
func main() {
if true {
x := 10 // local to this if-block
fmt.Println("Inside block:", x)
}
// fmt.Println(x) // ❌ ERROR: x not in scope here
}
RAM Impact:
xlives on the stack.- When the block ends, stack memory is reclaimed.
- No heap allocation unless a pointer/reference escapes the block.
Function Scope (Local to a Function)
- Variables declared inside a function are visible only in that function.
- By default stored on the stack, unless escape analysis moves them to the heap.
package main
import "fmt"
func add(a, b int) int {
result := a + b // scoped to add()
return result
}
func main() {
fmt.Println(add(2, 3))
// fmt.Println(result) // ❌ ERROR: result not accessible here
}
RAM Impact:
a,b, andresultare stack variables.- When
add()finishes, stack memory is freed. - If we return a pointer to
result, Go allocates it on the heap.
Package Scope
- Variables declared outside any function are visible throughout the package.
- Stored in the global memory (data segment / heap).
- Persist for the entire runtime of the program.
- If the variable name starts with a capital letter, it’s exported and visible to other packages.
var globalMessage = "I am global!" // package scope
func printMessage() {
fmt.Println(globalMessage)
}
func main() {
printMessage() // ✅ Works
fmt.Println(globalMessage) // ✅ Works
}
globalMessageis declared outside all functions, so any function in the same file (or package) can use it.
greetings/greetings.go
package greetings
import "fmt"
// Exported variable (starts with Capital letter)
var Message = "Hello from Greetings package"
// Unexported variable (starts with lowercase letter)
var secret = "This is hidden"
// Exported function
func SayHello(name string) {
fmt.Println(Message, name)
}
// Unexported function
func whisper() {
fmt.Println(secret)
}
main.go
package main
import (
"fmt"
"myproject/greetings"
)
func main() {
fmt.Println(greetings.Message) // ✅ Allowed, exported
greetings.SayHello("Masum") // ✅ Allowed, exported function
// ❌ Not allowed (unexported)
// fmt.Println(greetings.secret)
// greetings.whisper()
}
File Scope (Imports, Unexported identifiers)
- If an identifier starts with a lowercase letter, it is only visible inside the same package file.
- Works like "private" access modifier.
// file1.go
package main
var hidden = 42 // only inside this package
// file2.go
package main
import "fmt"
func main() {
fmt.Println(hidden) // ✅ accessible within same package
}
RAM Impact:
- Same as package scope — stored globally in heap, persists for program runtime.
Global Scope (Exported Identifiers)
- If a name starts with a capital letter, it’s exported and visible to other packages.
// mylib/mylib.go
package mylib
var GlobalVar = 100 // Exported
// main.go
package main
import (
"fmt"
"mylib"
)
func main() {
fmt.Println(mylib.GlobalVar) // ✅ accessible globally
}
RAM Impact:
- Same as package scope — stored in global heap memory, persists for full runtime.
Lexical Scope (Nested Functions / Closures)
- Inner functions can access variables from outer functions.
- If captured, the variables are stored on the heap (escape analysis).
package main
import "fmt"
func outer() func() int {
x := 0
return func() int {
x++ // closure captures x
return x
}
}
func main() {
counter := outer()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
}
RAM Impact:
xescapes to the heap because the inner function (closure) needs it afterouter()ends.- Normally,
xwould die with the stack frame ofouter(), but escape analysis moves it to heap.
Summary of Scopes and Memory
| Scope Type | Lifetime | Memory Location | Example Use Case |
|---|---|---|---|
| Block Scope | Until block ends | Stack (unless escapes) | Temporary variables in if, for, {...} |
| Function Scope | Until function returns | Stack (or heap if escapes) | Function-local variables |
| Package Scope | Entire program runtime | Heap (global data segment) | Shared state across package |
| File Scope | Entire program runtime | Heap | Package-private variables |
| Global Scope | Entire program runtime | Heap | Exported vars/constants |
| Lexical Scope | As long as closure exists | Heap | Closures holding variables |
Variable Shadowing
- When a variable with the same name is declared in an inner scope, it hides the variable from the outer scope.
- The outer variable still exists, but is inaccessible in the shadowed area.
var msg = "Global message"
func main() {
fmt.Println(msg) // "Global message"
msg := "Local message" // shadows global msg
fmt.Println(msg) // "Local message"
{
msg := "Block message" // shadows local msg
fmt.Println(msg) // "Block message"
}
fmt.Println(msg) // "Local message" (block scope ended)
}
Execution with Memory & Scope
package main
import ("fmt")
var first = 5
var second = 10
func display(p int){
p = 15
first = 20
fmt.Println("Value of p is (In - Display) : ",p)
fmt.Println("Value of first is (In - Display) : ",first)
}
func anotherShow(m int){
fmt.Println("Value of a is (In - Another) : ",m)
}
func show(a int){
fmt.Println("Value of a is (In - Show) : ",a)
anotherShow((a))
}
func main(){
x := 5
fmt.Println("Value of x is (Pre - Main) : ",x)
{
fmt.Println("Value of x is (Pre - Local) : ",x)
x := 10
fmt.Println("Value of x is (Post - Local) : ",x)
}
fmt.Println("Value of x is (Post - Main) : ",x)
fmt.Println("Value of first is (Pre - Main) : ",first)
fmt.Println("Value of second is (Pre - Main) : ",second)
show(x)
display(x)
show(x)
fmt.Println("Value of first is (Pre - Main) : ",first)
fmt.Println("Value of second is (Pre - Main) : ",second)
fmt.Println("Value of x is (Last - Main) : ",x)
}
func init(){
fmt.Println("I'm init function")
}
1. Program Load (before main() runs)
Go’s runtime loads your package. It sets up package-scope variables and functions in global memory:
In RAM now (Global Context):
- Global Variables in Data Segment
first = 5second = 10
- Functions(code pointers, not variables) in Code Segment
displayanotherShowshowmain
These live for the entire program lifetime. These don’t live on the stack, they live in the global memory region.
2. Run init() Function Automatically
Before main(), Go always executes init().
- A stack frame for
init()is created. - Inside it, no local variables (except temporary for string printing).
- After printing, the stack frame is destroyed.
RAM after init():
- Global still has
first=5,second=10, functions intact. - Stack for
init()is popped (freed).
3. Start Execution (main function call)
Go runtime looks for main.main() and starts execution.
A new stack frame for main() is created.
- Stack (main’s frame)
- local variable
x := 5
- local variable
4. Enter Block Scope { ... } inside main
- Creates a new inner block context within
main’s stack frame. - At entry: it sees the outer
x = 5and prints that. - Then
x := 10→ new variable shadows outerx.
So now stack has:
Outer x = 5 (main)
Inner x = 10 (block scope, shadows outer)
- Prints
10. - When block ends → inner
xis destroyed, only outerx=5remains.
5. Back in main after block
- Still has
x=5(outer one). - Prints
5.
Then prints first=5, second=10 from global scope.
6. Call show(x)
At this point x=5.
So check weather show() is exist in code segment or not, if exist show(5) is called.
- New stack frame for show is created
7. Call anotherShow(m)
New stack frame for anotherShow
Prints:
Value of a is (In - Another): 5
- Then returns → stack frame destroyed.
Back to show, then return → show frame destroyed.
8. Call display(x)
Now still x=5 in main.
So display(5) is called.
- New stack frame for display
Inside display:
p = 15 // local only, doesn’t affect main’s x
first = 20 // modifies global "first"
Now memory is:
- Global:
first=20,second=10 - Stack (display):
p=15
After return → p destroyed, but global first=20 stays.
9. Call show(x) again
Main’s x is still 5.
So again show(5) → anotherShow(5) like before.
10. Back to main
Value of first is (Pre - Main): 20 // because display() changed it
Value of second is (Pre - Main): 10
Value of x is (Last - Main): 5
Execution Summary
- Global Context (lives whole program):
→ stored in global memory/heap.
first = 20 (modified during display)
second = 10
display(), anotherShow(), show(), main(), init() - Stack during runtime:
main()createsx=5.- Inner block creates temporary
x=10(shadow), destroyed after block. - Each function call (
show,anotherShow,display) makes new stack frames. - On return, stack frames are freed.
- Variable Lifetime:
first,second→ live till program ends.x→ lives tillmainends.- Inner block
x→ lives till block ends. - Parameters like
a,m,p→ live only during function calls.