The Basics of Pointers in Programming Languages - How to Use Pointers in Golang

jason on June 6, 2025, 04:23 PM

So, what is the whole point of... well Pointers? Maybe you're new to programming in general or have been programming for a while and have never needed to use them, which is the case for languages like Python and Javascript since they don't have them. So why don't these languages have pointers? The simple answer is that pointers give you a deeper level of control over managing memory in your program and interpreted languages like Python and Javascript don't allow you to have that level of control. That's actually a feature of those languages, not a bug. This is because it's extremely easy to accidentally introduce all kinds of strange bugs into your program when managing the memory directly. So why would anyone wanna torture themselves by managing the memory directly? Another simple answer, speed. When you're manipulating the computer's memory directly without anything standing in the middle of the processing, things can move much faster. One prime example of this is in video games, where you want the program to run as close to real time as possible. These types of programs are called 'near realtime' because they're not actually real time, but that's a topic for another post!

So first, let's take a look at the elephant in the room. The strange looking syntax, that asterisk. You may have come across some code before that looks something like this (oh and in these examples, we're gonna use Golang because I like it and it's a bit easier to understand than something like C or C++):

var my_var *int

So what's the difference between that and this?

var my_var int

In the first example, we're basically saying. "Let me declare a variable named my_var that is a pointer to a variable whose type is integer". The second is just saying "Let me declare a variable whose type is integer".

But what does this mean? And how does this difference translate into different behavior in your code? Well, to understand, we have to introduce another topic, memory addresses.

Addresses

I won't go into too much detail in this post, but the main thing you have to understand is that each individual variable in your program is stored in its own actual physical location in the computer's memory, called a memory address. When your program reads or updates a variable, it directly interacts with these locations. Because the program has to know which address to go grab a variable from, or to update it at, the locations have to have addresses. You can think of them like addresses of houses on a street. When mail is sent to your house, the post office only needs to know your address, which allows them to deliver mail to or pick it up from your house. Pointers are variables that store the addresses of these memory locations. So when you declare a variable as a pointer, like we did in the first example above, that address is just storing the address of another variable instead of a value like an integer.

All variables in programs have a specific structure and size. These properties are especially important to keep in mind when that data is being passed around in the computer's memory and you need things to be efficient. You don't really need to worry much about this for simple data types like integers because a single address will be responsible for holding the entire variable. So expanding our above example a bit, we also could do something like this:

var my_var *int = 42

Here, we're telling the computer "Let me declare a variable named my_var that holds the address of a variable whose type is integer, and while you're at it, put the value 42 in the location at the address that we're pointing to".

The reason we can do this is because putting the asterisk in front of a variable when declaring it means we are storing the address of, or another way to look at it is a reference to another variable. In this case the address that we're storing is the one that is automatically assigned to hold the value 42. Putting the asterisk in front a variable name that is already declared as a pointer will let us do what is called de-referencing, which means 'give me the actual value that this pointer references.' So you can also do this:

var my_var *int
my_var *int = 42

This means, "update the location that my_var points to with the value 42. If you were to do this:

var my_var *int
my_var int = 42

You would be attempting to update my_var by overwriting the address stored in it, with the value 42, which is not what you want. So in order to properly update the value that is being pointed to, you must de-reference it when assigning or re-assigning a value.

As a side note, I didn't mention it before but pointers still must have a type associated with them. So if you want your variable to point to an integer, you can only put integers in the location that the address points to. There are a few reasons the compiler enforces this, but the main one is because de-referencing the pointer 'converts' it back to the data type stored at the address. Because memory locations store data in binary format, your program needs to know what the type of the value is so that it can convert it properly when it is dereferenced.

If we were to again, simply declare a variable the normal way, like this:

var my_var int = 42

Even though we didn't specify anything about pointers, there's still an address where my_var is storing the value 42, we just don't see it... But we can if we want! To demonstrate this, I have to introduce another topic, the 'address of' operator.

The 'Address of' Operator

Ok, so we've seen with pointers, putting that asterisk in front of the variable type lets you know the variable you have declared is actually the address that another variable is stored at, and not an actual value. But, to get the address of any variable, you just have to put the ampersand symbol in front of it instead. That would look something like this:

var my_var int = 42
var my_vars_address = &my_var

now, if we were to print out the value of the variable my_vars_address, we would not see the value, 42. We'd instead see something like 0xFFFF. This is what a memory address looks like. It's really nothing fancy, just a plain old number written in hexadecimal format. The hex format doesn't really have anything to do with it being a memory address. Hex is just commonly used in computing to be able to write long numbers in a way that's shorter and easier for humans to identify. The computer actually stores all numbers in binary, but can output them in hexadecimal for us because it's easier on the eyes than binary is. But anyway, because my_vars_addresss is a variable that stores the address of my_var, it is a pointer.

The Point of it all

So, now that we understand that pointers are just variables that store the addresses of other variables in the computer's memory, and that we can get the address of a variable whenever we want... You might, ask: "Why would we need to have control over doing this?" So to understand this, we have to go a bit deeper into the explanation of different data types (don't worry, I'm keeping it simple, this post is not about data structures and algorithms.) So for all the examples so far, we used pointers that point to integers. But the thing with integers is, they only take up one memory address per variable so it's pretty much just as easy for the computer to pass the actual value of an integer around in memory as it is to pass around the address of it. But there are many cases in a program where the data being shuffled around actually takes up a lot of space so it needs many addresses. The prime examples are Arrays and Structs.

Arrays in memory

So the main thing to understand about arrays is that each value in an array is stored at a separate location in memory, although all elements of the array are stored in addresses that are lined up right next to each other. Another way to say this is that they are contiguous in memory. So when you write something like this:

var my_var [4]int = [4]int{42, 100, 67, 89} // declaring an array with 4 integers

What's happening under the hood is that each integer value in the array is stored at a separate address, but when the computer passes around the value of this array in memory, it doesn't necessarily need to carry around all the values. It only needs to know one thing, the address of the first element in the array (in this case the address of that 42). Since pointers can only store that one value, the address, you as the programmer are responsible for knowing the length of the array, (in this case, 4).

Here's where you have to watch out! Remember how I said earlier, that using pointers can make your program faster, but they can also let you do things that introduce weird bugs? Well, here's such a case that you have to watch out for. In a language like C, there's no memory safety built in, so you would just able to access the address of a memory location 'out of bounds' of your array. This would cause 'undefined' behavior, because there's no telling what is stored in the addresses right behind the last array element. Thankfully, Go has some memory safety built in for mistakes like this and will just result in a panic. Here's an example to illustrate what I mean:

var my_var [4]int = [4]int{42, 100, 67, 89}
fmt.Println(my_var[4]) // Your program will panic! We don't know what is in the address after 89!

This is also why arrays can't be resized once they're created. If you were to add another element to this array, there's a chance you could accidentally overwrite the value of some other variable that just happens to be stored at an address after it.

So, you might be thinking to yourself, "But Python and Javascript let me resize arrays... how do they do that?" There's more to it than this, but essentially they are deleting and re-creating new arrays behind the scenes whenever you resize one that you've declared. This is why in Golang, there's the distinction between Slices and Arrays. Arrays are more memory efficient, but you can only use them if you don't need to re-size the array later. Slices are a convenient way for you to have re-sizeable Arrays, but they come at an extra computational cost because of the work Go is doing in the background to constantly destroy and create real arrays for you.

So, the cool thing about pointers is that you only need to pass around a pointer to your array (or struct, or whatever) instead of having to put the entire object value in memory. this is much more efficient because an address is so small and lightweight. This way, your array values can all stay in the same memory location even though you're passing around a reference to it.

Passing around arrays in memory might not seem like a huge deal, but you have to keep in mind that arrays can be quite large. For example, strings are actually 'character' arrays under the hood. So long strings (like this blog post) are just really big arrays. If you have to pass such a long string to another function, being able to pass a pointer to it can be quite useful.

Structs

So the last topic I'll cover in regard to pointers is struct types. Of course structs can also be pretty large objects in memory so there is a lot of efficiency that can be gained when using pointers instead of passing around the entire values, just like with arrays. When accessing the value of a struct member variable, you normally would have to de-reference the pointer so you can get the actual struct value, then access the member variable like this:

type MyStruct struct {
     SomeNumber int
     SomeString string
}

var the_string = (*MyStruct).SomeString

fmt.Println(the_string)

but luckily, Go makes it a bit easier on us and lets us do this instead. Basically the dereferencing is automatic.

var the_string = MyStruct.SomeString
fmt.Println(the_string)

So, to recap: A pointer is just a variable that holds a memory address of another variable. You can use them when you don't want to pass around a large data structure, like an array or struct in memory. Be careful to not accidentally access memory addresses out of bounds of your arrays.

See! Pointers aren't really all that scary.

Share this Post

2
167
0