Friday, February 6, 2026
Variables and Data Types
Akash AmanUpdated: February 2026
Variables
Concept: Go variables are statically typed. The := operator is for declaration + assignment, while = is only for assignment. var keyword can also be used for declaration or assignment.
Why: Go enforces strict typing at compile time to prevent runtime errors like adding a string to an integer.
- Shadowing (Crucial): Using
:=inside a block (likeif) creates a new variable that hides the outer one.
Constants
Concept: Constants are values that cannot change. They can be Typed or Untyped.
Why: Untyped constants have high precision and can be used flexibly with different types without explicit conversion.
The Precision Justification
- In many languages, if you define a constant like 3.14, the compiler immediately treats it as a float64. In Go, an untyped constant maintains extremely high precision (at least 256 bits) until it is actually assigned to a variable.
The Flexibility Justification (No Casting)
- Go is famous for being "strict." You cannot add an
int32to anint64without an explicit cast. However, untyped constants ignore this rule to make the code readable.
Data Types
In Go, a Value Type is a type where the variable directly contains its data. When you assign one to another, or pass it to a function, Go creates a complete, independent copy. If you change the copy, the original remains untouched. There are different value data type, i.e int, byte, rune, float, bool, string, array, struct.
Integer
Go is very explicit about integers to ensure memory efficiency, especially for low-level or IoT programming.
Signed Integers (int, int8, int16, int32, int64)
Concept: Can store both positive and negative numbers.
- Behavior: Uses "Two's Complement" logic. The most significant bit determines the sign.
- Why: Go provides fixed sizes so you can match the data structure of a file format or network protocol exactly without wasting bits.
Unsigned Integers (uint, uint8, uint16, uint32, uint64, uintptr)
Concept: Can only store positive numbers (0 and up).
- Special Alias:
byteis an alias foruint8. - Why: Used when you need to ensure a value is never negative (like a memory address or a pixel color value).
uintptris specifically for storing the bit pattern of a memory pointer.
In Go, an int16 and an int32 are completely different types. You cannot add them together without a manual conversion.
Behavioral Reason: Go prioritizes Predictability. If the compiler automatically "upgraded" an int16 to an int32, you might accidentally consume more memory than intended or cause overflow issues when down-casting later.
Comparison Table
| Type | Size (Bits) | Range / Use Case | Zero Value |
|---|---|---|---|
| bool | 8 | true, false | false |
| int8 / uint8 | 8 | -128 to 127 / 0 to 255 | 0 |
| int16 / uint16 | 16 | Β±32,768 / 0 to 65,535 | 0 |
| int32 / uint32 | 32 | Β±2.1 Billion / 0 to 4.2 Billion | 0 |
| int64 / uint64 | 64 | Massive ranges (for high precision) | 0 |
| int / uint | 32 or 64 | Platform dependent (Matches CPU architecture) | 0 |
| string | 128 (64-bit) | Immutable pointer + length header | "" |
Boolean
Concept: Represents a simple true or false, Unlike JavaScript or Python.
- Zero Value:
false. - Strictness: Go is one of the few languages that does not allow "truthiness." You cannot use an integer as a boolean. Go does not have "truthy" or "falsy" values. An integer
0is notfalse, and an empty string is notfalse.
Behavioral Reason: This prevents the "logic bugs" common in other languages where a value of 0 might accidentally trigger an error block.
String Types
Concept: A read-only slice of bytes representing text.
- Internal Structure: A "Header" containing a Pointer to a backing array and a Length.
- Zero Value:
""(Empty string).
Immuntability (The "Why")
Behavior: You can reassign a string variable, but you cannot change its individual characters.
Reason:
- Safety: Multiple variables can point to the same underlying text in memory without fear that one will change it for everyone else.
- Performance: Since the text is read-only, Go doesn't have to copy the actual text when you pass it to a functionβit only copies the tiny 16-byte header.
byte and rune
The byte (uint8)
Concept: A byte is an alias for uint8. It represents 8 bits of data.
Why: In Go, strings are internally just "slices of bytes." When you deal with ASCII characters (like 'A', 'B', '1'), a byte is enough to represent them.
The rune (int32)
Concept: A rune is an alias for int32.
Why: Go uses UTF-8 encoding. Characters like emojis (π) or non-English letters (Γ±, δΈ) require more than 8 bits. A rune is large enough (32 bits) to hold any Unicode character (a "Code Point").
Behavioral Example: Why the difference matters
- If you iterate over a string using a standard loop, you get bytes. If you use a
rangeloop, Go automatically decodes the bytes into runes.
Why this behavior?
- Efficiency: Storing everything as
int32(runes) would waste a massive amount of memory (4x more for standard English text). By using a byte-slice for strings and only converting torunewhen needed, Go stays incredibly fast and memory-efficient. - Pass-by-Value: Since both are just numbers (
uint8orint32), they follow the standard Value Type rule: when you pass abyteorruneto a function, a copy is made, and the original is safe.
Crux
byte= Use for raw data, ASCII, or binary files.rune= Use when you need to handle text/characters accurately (Unicode).- Memory: Both are Value Types and are very cheap to pass around.
In Go, Arrays are often the most misunderstood "Value Type" because they look like slices but behave completely differently in memory.
Arrays
Concept: Fixed Size as Identity
An array is a collection of elements of a single type with a fixed length.
- Key Rule: The size of the array is part of its type.
[3]intand[5]intare distinct, incompatible types. - Why: Go allocates a contiguous block of memory for exactly that many elements. This makes indexing incredibly fast because the CPU knows exactly where each element sits.
Behavioral Reason: Pure Pass-by-Value
Unlike many other languages (like C or Java) where "arrays" are implicitly pointers, in Go, arrays are values.
- The "Memory Tax": When you pass an array to a function, Go copies every single element into the function's scope.
- Why this behavior? It ensures total isolation. No matter what a function does to the array it receives, the caller's original array is physically impossible to change.
Initialization & Zero Value
- Zero Value: An array is never "nil." Its zero value is an array of the same length where every element is set to its own zero value.
- Efficiency Note: Because of the heavy "copying" cost, you will rarely see large arrays passed directly in Go. Instead, we use Slices (which point to arrays) or Pointers to Arrays.
Crux
- Definition: A fixed-length, contiguous block of memory.
- Identity: Size + Type = Identity. You cannot pass
[4]intinto a function expecting[3]int. - Performance Gotcha: Passing an array of 1 million integers copies 8MB of data. If you don't want to copy, pass a Slice or a Pointer.
Reference Type vs. Value Type
Go categorizes types by how they are handled in memory:
- Value Types: Store the actual data (int, float, bool, string, array, struct).
- Reference Types: Store a pointer to the data (slice, map, channel, pointer, function).
The Golden Rule: "Everything is a Copy"
- In Go, when you pass a variable to a function, Go always makes a copy of whatever is in that variableβs "box."
Value Types (Copying the House)
- Types:
int,byte,rune,float,bool,string,array,struct. - When you pass these, Go copies the entire data. If you have an array of 1 million integers, Go copies all 1 million integers into the function's memory.
- Behavioral Reason: This ensures Isolation. The function cannot accidentally change the original data, making code easier to reason about in concurrent (multi-threaded) environments.
Reference Types (Copying the Key)
- Types:
slice,map,channel,pointer,interface. - These types are actually small headers (structs) that contain a pointer to an underlying data structure. When you pass a slice, Go copies the header (the pointer, the length, and the capacity), but not the underlying data (the backing array).
- Behavioral Reason: This ensures Efficiency. You can pass a slice representing a 1GB file, and Go only copies about 24 bytes of "header" info, while still allowing the function to modify the actual file data.
Crucial Concept: The "Pointer" Exception
- If you want to modify a Value Type (like a struct or int) inside a function, you must pass a Pointer.
- Why: You are changing the variable's "box" from containing the "Data" to containing the "Memory Address." Go copies the address, and the function follows that address back to the original house.
Concept: String Headers (The Hybrid)
Strings are technically Value Types, but they behave efficiently.
- The Structure: A string header contains a pointer to the bytes and a length.
- The Catch: Strings are immutable.
- Behavior: When you pass a string, Go copies the header (cheap!), but because it's immutable, you can't change the underlying bytes. This gives you the speed of a reference type with the safety of a value type.
Summary
| Type Category | What is Copied? | Impact of Mutation | Impact of Reassignment | Memory Cost |
|---|---|---|---|---|
| Value (int, byte, rune) | Entire Data | Original is safe | Local only | Very Low (1β4 bytes) |
| Value (Array) | The entire data set | Original is safe | High (increases with size) | |
| Reference (slice, map) | Header (Pointer) | Original changes | Local only | Low (fixed) |
| Pointer (*int, *struct) | Memory Address | Original changes | Original changes | Low (8 bytes) |
| String | Header (Pointer) | Impossible (Read-only) | Local only | Low (fixed) |
Zero Value
Concept: Variables declared without a value automatically get a "Zero Value." Why: This prevents "garbage memory" bugs common in C.
int:0bool:falsestring:""(empty string)pointers/slices/maps:nil
Initialization
In Go, Initialization is the process of preparing a variable for use. Understanding the difference between var, new, and make is critical because using the wrong one can lead to the most famous Go error: panic: runtime error: invalid memory address or nil pointer dereference.
Value Types (Automatic Initialization)
Value types (int, bool, struct, array) are never "empty." When you declare them, Go automatically allocates memory and fills it with the Zero Value.
- Behavior: They are ready to use immediately upon declaration.
- Mechanism: Memory is usually allocated on the Stack.
Reference Types (The nil Problem)
Reference types (slice, map, chan) store a header that points to an underlying data structure.
- Behavior: When declared with
var, the header's pointer isnil. - The Trap: You cannot store data in a
nilmap or channel; it will cause a crash.
new() vs make()
To handle initialization, Go provides two built-in functions that serve completely different purposes.
The new Function
- What it does: Allocates memory for a type and returns a pointer to it. It sets the memory to the zero value.
- Result: Returns
*T(a pointer). - When to use: Use it when you need a pointer to a value type (like a struct) to share it across functions.
The make Function
- What it does: Initializes Reference Types only (slice, map, channel). Unlike
new, it creates the internal data structure (the backing array or hash table) so the header is no longernil.makealso allows you to pre-allocate memory. - Result: Returns
T(the actual type, not a pointer). - When to use: Always use
makefor maps and channels. Use it for slices when you know the initial size or capacity.
The Big Difference: var vs make
This is the most important "behavioral reason" to include in your Initialization section:
| Feature | var s []int (Declaration) | s := make([]int, 3) (Initialization) |
|---|---|---|
| The Header | Exists, but Pointer is nil | Exists and points to an array |
| The Memory | No backing array allocated | Backing array allocated |
| The Elements | No elements exist | Elements initialized to Zero Value |
Examples
Slices: Zeroing the Backing Array
When you use make for a slice, Go does two things:
- It creates the Header (Pointer, Length, Capacity).
- It allocates a Backing Array and fills every slot with the Zero Value of the element type.
Why? Go ensures memory safety. If it didn't zero out that array, your slice would contain "garbage data" left over from whatever was in that memory previously.
Maps and Channels: Preparing the "Room"
For maps and channels, make initializes the internal data structure so it is ready to receive data.
- Maps: It creates the hash table buckets. The keys and values don't exist yet, so there are no "zero values" to see until you actually start adding keys.
- Channels: It creates the buffer. If it's a buffered channel, the slots in that buffer are essentially "empty," but memory is reserved.
Comparison
| Feature | new(T) | make(T, args) |
|---|---|---|
| Applicable Types | All types (int, struct, etc.) | Only Slice, Map, Channel |
| Return Type | Pointer (*T) | Value (T) |
| Memory State | Zeroed (but pointers inside may be nil) | Fully Initialized (ready to use) |
| Goal | Get a pointer to a zero-value | Initialize internal data structures |
Behavioral Reasoning
- Indirection vs. Initialization:
newis a generic allocator. It doesn't know anything about the "internals" of a type; it just gives you a clean slot of memory. - Complex Internals: Slices, Maps, and Channels are complex. They aren't just memory slots; they require a "backing" structure (like an array for a slice or a hash table for a map).
makeis a specialized constructor that sets up these hidden parts so you don't have to manage them manually.
"Does make assign zero values? Yes. For slices, it populates the length of the slice with the type's zero value. For maps and channels, it initializes the internal machinery so they are no longer nil and are ready for use."
- The Golden Rule: If you want a Pointer to an object β use
newor&Type{}. If you want a Slice, Map, or Channel β usemake.
Type Conversion
Concept: Moving a value from one type to another. In Go, this must be done explicitly.
Why: Go does not have "Implicit Conversion" (where the compiler automatically changes an int to a float). This prevents hidden precision loss or bugs that occur in languages like C++ or JavaScript.
Behavioral Reason: By forcing you to write the conversion, Go ensures you are aware that you might be losing data (like converting a float64 to an int which chops off the decimals).
Defined Types
Concept: Creating a brand new type based on an existing one.
Why: This allows you to add methods to a type and creates "Domain Logic" safety. Even if the underlying data is the same (e.g., both are int), a Duration is not the same as a Temperature.
Behavioral Reason: Go treats Celsius and Fahrenheit as totally different types. This prevents you from accidentally adding degrees Celsius to degrees Fahrenheit in your code, which would be a logical disaster.
Alias Types
Concept: Creating an alternative name for an existing type. Use the = symbol.
Why: Aliases are used mostly for Refactoring. If you move a type from one package to another, you can leave an alias behind so old code doesn't break.
Behavioral Reason: An alias is not a new type. It is just a second name for the same thing. The compiler treats Kilometers and float64 as identical.
Summary
| Feature | Defined Type (type T1 T2) | Alias Type (type T1 = T2) |
|---|---|---|
| Is it a new type? | Yes (Distinct identity) | No (Just a nickname) |
| Implicit Conversion? | No (Must cast manually) | Yes (They are the same) |
| Can add methods? | Yes | No (Only on the original type) |
| Use Case | Domain logic, Type safety | Code refactoring, Package migration |