Skip to main content

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:

  • x lives 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, and result are 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
}
  • globalMessage is 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:

  • x escapes to the heap because the inner function (closure) needs it after outer() ends.
  • Normally, x would die with the stack frame of outer(), but escape analysis moves it to heap.

Summary of Scopes and Memory​

Scope TypeLifetimeMemory LocationExample Use Case
Block ScopeUntil block endsStack (unless escapes)Temporary variables in if, for, {...}
Function ScopeUntil function returnsStack (or heap if escapes)Function-local variables
Package ScopeEntire program runtimeHeap (global data segment)Shared state across package
File ScopeEntire program runtimeHeapPackage-private variables
Global ScopeEntire program runtimeHeapExported vars/constants
Lexical ScopeAs long as closure existsHeapClosures 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 = 5
    • second = 10
  • Functions(code pointers, not variables) in Code Segment
    • display
    • anotherShow
    • show
    • main

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

4. Enter Block Scope { ... } inside main​

  • Creates a new inner block context within main’s stack frame.
  • At entry: it sees the outer x = 5 and prints that.
  • Then x := 10 → new variable shadows outer x.

So now stack has:

Outer x = 5 (main)
Inner x = 10 (block scope, shadows outer)
  • Prints 10.
  • When block ends → inner x is destroyed, only outer x=5 remains.

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​

  1. Global Context (lives whole program):
    first = 20 (modified during display)
    second = 10
    display(), anotherShow(), show(), main(), init()
    → stored in global memory/heap.
  2. Stack during runtime:
    • main() creates x=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.
  3. Variable Lifetime:
    • first, second → live till program ends.
    • x → lives till main ends.
    • Inner block x → lives till block ends.
    • Parameters like a, m, p → live only during function calls.

Resource​