← Back to blogYouTube Video

Published October 20, 2025

Memory Management in Go - Part 1

Part 1 of 2 in Memory Management in Go

Can't play the video or having issues? Here's the direct link.

Summary

Go runs a garbage collector, but underneath that every variable ends up either on the stack or the heap, and the compiler chooses using escape analysis. I start with why memory management matters at all — C and C++ force manual allocate and free, Rust's ownership model makes the compiler handle it, and Go automates it with a garbage collector — then go deep into the two structures Go actually uses. The stack holds local variables in LIFO order, is automatically managed, and needs no garbage collector; the heap is slower, stores objects behind pointers kept on the stack, and is where garbage collection runs. I walk through the escape analysis rules the compiler applies to every variable: returning a pointer, using maps, slices or structs, closures capturing outer values, and storing values inside complex objects all push data to the heap. Stack allocation is much faster, so these rules matter if you're building performance-critical Go.

Key takeaways

  • Escape analysis decides stack versus heap: values that escape their function go on the heap, everything else stays on the fast, automatically managed stack.
  • Returning a pointer to a value forces that value onto the heap — once the function returns, the stack copy is gone and the pointer would be invalid.
  • Maps, slices and structs always live on the heap, even simple ones, so treat them as heap allocations when reasoning about performance.
  • Closures that capture outer variables push those variables to the heap so they outlive the enclosing function.
  • Values stored inside complex objects — a field in a struct, an element appended to a slice — escape to the heap along with their container.
  • The stack is significantly faster than the heap: it's LIFO, needs no garbage collector, and freeing memory is just popping the top stack frame.

Description

Learn how GoLang manages memory under the hood.

Transcript

Hey guys, what's up? How is everybody? It's been so long since I sat down to record a video because my university opened up and I just have classes all the time, and I just can't find the time to sit down and record a video until now. Through some miracle by God, I found some extra spare time that I can use right now to record a video, and that's what we're gonna do. In this video, I'm going to teach you how memory management works in Golang. So you guys know I'm learning Go and building all sorts of projects in it. We recently finished building the Redis clone in Go, and yeah, I'm learning as much as I can about this language. In this video, I'm going to be teaching you how memory works inside Go. So this is going to be a two-part video because there is a lot of stuff we need to cover. I'm going to go very deep into this topic, and so, like, this is not going to be a high-level overview. We are literally going to go very deep into this topic and study the inner workings of how memory is managed in Go, and so we need to divide this video into two parts. So let's get started. First of all, I just want to talk about why memory management is even important. So why is memory management important? And by the way, as you guys can see, I am using this website called Excalidraw for building these, I guess, presentations live, just so it's easier for you guys to understand what I'm saying, right? Because rather than me just saying things, you can also see the text over here and everything else. And I guess that makes it easier to learn. Anyway, why is memory management important? Well, because when you're writing a program, you're obviously going to be declaring variables, storing data inside those variables, and just processing that data and so on and so forth, right? So you need access to the system memory so you can store that data. Now, if you just keep storing and keep adding more and more data into the memory without actually clearing it, right, without actually deleting the unused data, then over time, the memory is just going to overflow, right? You're just going to run out of system memory, and your program is going to crash, right? Obviously. So we need some sort of method or approach to manage the memory that our program is using, right? We need it so we can allocate more memory to store more data. We need it so we can free up some memory used by previous unused data, and so on and so forth, right? Now, different languages manage memory differently, right? So we have something like, I guess, C or C++, where you have to manually manage your memory, right? So you have to basically just allocate and free up memory by yourself, right? You have to manually do this. And this can be very cumbersome because you have to manually do it. You have to manually remember to free up any memory after you're done using some variable in something, right? And a lot of times you can just forget to do that, right? We're humans. So manual memory management in C and C++ is prone to a lot of bugs, right? Like, just Google something like C memory leaks, and you'll find, I don't know, hundreds, thousands of cases, literally thousands of cases where this issue arises. So then you also have languages like Rust, which are gaining a lot of popularity, and they are also, like, Rust is also kind of a manual memory management. Now, the way Rust handles memory management is it does something called the ownership model, which is kind of still manual, right? Because you're still manually managing the memory. But the difference is that this ownership model will basically let the compiler handle all of the memory for you, in that as soon as you're done using a variable, it will automatically clear it and no sooner and no later than that, right? I'm not going to go into too much detail about the ownership model, but it is kind of manual in that you need to write your programs in a way that Rust's compiler will be satisfied, right? The ownership model in the Rust compiler will be satisfied, but it is still an improvement over C and C++ because when you use Rust and when you use the ownership model of Rust, you basically cannot have any memory issues at all, right? And then there's also languages like Go, Python, JavaScript, TypeScript, all of these other scripting languages that use a garbage collector. So I'll say garbage collection over here. Now, garbage collection is probably the easiest way to manage memory in your program because all it really does is you just write your programs as normal, and this garbage collector will run on its own, and it will basically just check which variables, which data is still in use, and it'll just leave those there. And the data that you're not using, the variables that are unused, it'll just free them up in memory, delete them, and free up the memory that is used by them. So this garbage collection is the automatic way to manage memory, and it also what Go uses. So that what we going to talk about. Now we learned that Go uses a garbage collector to manage memory, but let's go one level deeper. How exactly does Go represent memory in its programs? Because look, memory at the most fundamental level is just a bunch of RAM chips inside your computer, right? Inside your CPU. Just a bunch of RAM chips attached to your motherboard, right? How does that, how does Go represent that in its programs? So the way it does that is it basically uses two memory structures, two data structures called, one of them is the stack, all right, and the other one, I'll put it over here, is the heap. So let's talk about what both of these are and how they work. And let me just change the text color as well because I don't like blue over here. All right, so let's talk about the stack first. The stack is the simplest data structure to understand out of these two. So we'll start with that. Now the stack is used to store local variables declared in functions. All right. So let me show you a quick example over here. I have this file over here called stack.go, and it has a bunch of functions: first, second, and third and fourth. And they just each call the next function, right? So, for instance, if I call the first function and the first function declares some variables like x equals 1, right, and y equals 2, this is going to be stored on the stack, all right? If I do the same thing in the second and the third functions, it's going to be stored on the stack because these are local variables to the functions. A stack is basically like a stack of plates, right? So if I draw something like that over here, like something like this, right, you have a bunch of plates on top of it, right? You can think of every single plate over here as a function. You call one function, it's going to declare some variables, store some data, and all of that will be stored on the stack. You call the next function, it's going to do the same thing. You call the next function after that, it's going to do the same thing. And then, as soon as the execution of this function is over, all of the variables declared in this stack frame are going to be wiped out, all right? So, for instance, if I call the first function, it calls the second function, then the third, and then the fourth, right? And then over here in the fourth function, if I have a variable called something like x over here, right, as soon as this fourth function has been finished executing, this x variable is going to be completely removed from memory, all right? So that's how the stack works. You basically have a stack of functions where every single piece in the stack stores the data inside that function, and then as soon as the function is finished executing, all of the data inside that function will be wiped out by the stack. By the way, this nil pointer dereference, we don't need to worry about that right now. That we're going to cover that in just a bit. But just understand that a stack is like a stack of plates where each plate is a function call, and every time the function finishes executing, all of the data inside that function, all of the entire, the entire stack frame is just wiped out, all right? It's just wiped out just like that. So that's how memory management works inside the stack. You can see that it's very, like, automatic. We don't need a garbage collector to manage memory inside the stack. So let's write all that down, right? Automatically managed, doesn't need a garbage collector, and also it's super fast. Like, if you're storing data inside the stack, it's going to be very, very fast to store and retrieve data because it's just going to be at the top of the stack, right? Whatever function you're using over here, like whatever function is being called, if you're running the fourth function, we know that whatever data we store over here is going to be at the top of the stack, and we can just retrieve it quickly, right? When we want to clear data from the stack, we know that as soon as this function finishes executing, we just need to remove the top element in the stack, right? And then we go back to the third function, we do the same thing. Then in the second, we do the same thing, and back in the first, we do the same thing. And it's just super fast and super easy to manage memory on the stack. I can also just remove all of these variables, go back to main.go, and by the way, you can ignore all of this. This is just some unrelated code. If I run the first function in the stack over here, you'll also see a stack frame. So let me run this, and you probably know what all of this is. You've probably seen this in other languages as well. This is a stack frame, and what this stack frame basically tells us is where the program started executing, which functions it called, and where it failed, right? So you can see that it first started by, I call them the main function over here. Then the first, second, third, and fourth. And the fourth function is where the program crashed, right, because of this nil pointer dereference error that we placed over here, right? So this is what you can kind of think of as a stack. Every single function over here is one more element inside the stack. As soon as the function finishes executing, we remove it from the stack, and all of the data inside that function is wiped out, right? And so that's how the stack works. Now let's talk about the heap because the heap is where garbage collection actually happens. So the heap is, let me type that out over here as well, the heap is much slower than the stack, and it uses a garbage collector to clear memory. And the way that objects are stored in the heap is basically you store the object data inside the heap and then store a pointer to that object inside the stack, all right? And let me just, there we go, yeah. So basically, when you try to store data inside the heap, or actually when Go decides to do that because you don't manually do it, Go does it for you, when Go stores an object inside the heap, it's basically storing that object inside some random place in memory, and it returns a pointer to that object, which it stores on the stack, on the stack of whatever function you're currently executing. Right now, you're probably wondering, because we've discussed the differences between stack and heap and we know how memory is managed on both, now you're probably wondering, how does Go decide when to store a variable on the stack or on the heap? And basically, there's a couple of rules that Go uses, and those rules are called escape analysis, all right? So let's place that over here as well. Call it escape analysis. And basically, escape analysis is just a bunch of rules that the Go compiler runs on every single function, every single piece of data, to decide whether it escapes that function. If it escapes that function, then it's going to store it on the heap. It's going to store that object on the heap. And if that object does not escape the function, then it's going to store that on the stack. So simple variables like, you know, x equals 5 or something, y equals, I don't know, 10, 15. These are very simple variables, and they're not going to escape the function, right? We're not going to be using these variables as soon as this first function has finished executing, right? And so they're not going to escape the function, and they're going to be stored on the stack, all right? Now I have a bunch of different examples of escape analysis rules over here, and we can go through each of them right now. So basically, a stack uses the LIFO data structure, LIFO, last in, first out. So the last element that was added in will be the first one to leave out. Basically, every time you run a function, it's going to create a new stack frame and everything, right? It's going to run all these functions over here. It's going to go to the fourth function. This is the function that was last in, right? This is the last function that we're running, and so this will be the first function that we throw out, right? Because as soon as this finishes executing, we're going to throw its stack frame out, and then this third one, and then the second one, and then so on, right? So the stack is a last First-in, first-out data structure, whereas the heap is a lot more random, right? There's no order in how elements are stored or removed inside the heap. Stack is an ordered data structure. Heap is not. So let's go through each of these rules one by one. First of all, very simple: if you're returning a pointer to a value, then that value is escaping, and it must live on the heap. Because look in this `foo` function over here, where, and let me just zoom in as well. That's probably easier for you guys to see. I guess that's enough. In this `foo` function over here, we're returning a pointer to an integer, right? We're declaring an integer, and we're returning a pointer to it. Now, even though after this function is finished, the `x` variable is not really needed anymore, we're still returning a pointer to that `x` variable, and we need to store `x` on the heap. Because if we don't, if we store `x` on the stack, it's just going to get cleared, right? It's going to get deleted as soon as this function is finished executing, and then this pointer that points to the `x` variable will be invalid, right? And Go does not allow invalid pointers, right? So values must be stored on the heap if you are using a pointer to them. If you're returning a pointer to a value, it must be stored on the heap, right? Because `x` is escaping, and the value of `x` is still being used by this pointer even after this function has finished executing. So that's the first example. If you're returning a pointer or using a pointer to a value, then the value must be stored on the heap. Secondly, if you're returning complex objects or if you're using complex objects in your code, such as a map, a slice, or a struct, then all of these are going to be stored on the heap. Every single one of them. Maps are stored on the heap, slices are stored on the heap, this user struct, any struct is going to be stored on the heap. Even if it's just a super simple struct like this user struct, where all we really store is a name, it's still going to be stored on the heap. Now, moving on, here we have a counter function, and it returns another function which references a value in the outer function. So basically, in the counter function here, we declare a variable `x`, then we return a function that uses that value `x`, right? `x` is declared in the counter function, not in this sub-function over here that we return, right? And so, if we return this function, let me just show you quickly over here. Let's say `f` will be the function, and we run the counter function over here, like this. There we go. This is going to return a function that returns an integer, right? There we go. Since this inner function is using the `x` variable that is declared up here in the counter, `x` is escaping, and it must live on the heap. Because once the execution of this counter function ends, `x` will be removed, and then this function will be invalid because it's trying to use an invalid `x` variable over here, right? Which is why `x` is escaping, which is why it must live on the heap. So if you're using an inner function that is referencing outer function values, like this `x` variable over here, then this `x` variable cannot be stored on the stack. It must be stored on the heap so it outlives this function. Now, moving on from that as well, I guess this is the final example that we need to show. So any objects that you store inside a complex object, like a slice, a map, a struct, etc., are going to be stored on the heap themselves as well. So here we have a slice of integers. We have an `x` variable, and we append it to the slice. When we do this, `x` is going to escape the stack and live on the heap because the slice itself is also going to live on the heap. Similar to this other example up here, where we create a new user struct, right? Since user is stored on the heap, this name field, this name string, is also going to be stored on the heap because it is part of a complex object, a struct over here that is stored on the heap. All right, so these are a bunch of rules about escape analysis and how Go, the Go compiler, decides which objects to store on the stack and which objects to store on the heap. It basically runs all of these rules on every single variable that you declare in your code, and it decides: Does this value escape the function that is calling it, that is storing it, or does it not escape that function? If it does escape, it's going to store it on the heap. Otherwise, it's going to store it on the stack. And remember, the stack is a much faster data structure. It's automatically managed. It does not need a garbage collector. The heap is the complete opposite of that. It's kind of fast still, but it's a lot slower than the stack. It does need a garbage collector, so there is a performance overhead of using a garbage collector, and it just kind of slows down your program a little bit. And if you're building really performance-critical or speed-critical programs, then you're going to have to pay a lot of attention to this topic. But anyway, we talked about memory management in Go. We talked about how Go manages memory through garbage collection. We talked about the two different data structures that Go uses, the stack and the heap, to manage memory. And we also talked about escape analysis and the rules of escape analysis that the Go compiler uses to decide which variables to store on the heap and which to store on the stack. This was part one of this two-part video on memory management in Go. In the second part of this video series, I guess it's not even a series. It's just a two-part video. But in the second part, we're going to talk about how the garbage collector in Go actually works. So how the Go garbage collector actually works. We're going to talk about how it runs, when it runs, what kind of algorithms it uses to find the unused memory, find the used memory, and so on and so forth, right? How the garbage collector actually processes every single variable, all of the data inside your program, and how it decides which to throw out, which to keep, and so on and so forth. We're going to discuss all of the algorithms and the methods and everything that the garbage collector uses in the next part of this video. But for now, we're done with this. Thank you so much for watching. Like, comment, subscribe, share, all of those wonderful things. And I will see you in the next video where we will discuss how the Go compiler or the Go garbage collector actually works.

Share this article

Hassan Aziz pointing up

Dude, you’re at the bottom of my landing page.

Just book a call already if you’re that interested.

You scrolled all the way here.