I think some programming languages make this easier to understand than others. I'll demonstrate with Java.
A basic Java program might look something like this:
public static void main(String[] args) {
int x = 1;
int y = 2;
int z = 3;
System.out.println(x + y + z); //6
}
Now, the computer running the program has to store those variables somewhere.
In this example, the x, y, and z variables are all ints. Because this is a primitive type, it has a fixed size: an int always has 32 bits, or 4 bytes.
Because of this, when the computer goes to execute the function, it knows exactly how much memory space it will take up. Because of this, it can use a data structure called a stack to store these variables very efficiently.
So, the key things about the stack are that (a) it stores primitive types of fixed size, and (b) it's fast to access and write to.
Now, let's take another example program:
public static void main(String[] args) {
ArrayList<Integer> myArray = new ArrayList<>();
myArray.add(1);
myArray.add(2);
myArray.add(3);
System.out.println(myArray.get(0));// 1
}
In this program we create an ArrayList. The difference here is that myArray is not a primitive type like the ints we had before. As you can see with our repeated add calls, the amount of memory used by the array can increase and decrease.
This means that non-primitive values can not be stored on the stack. Because of this, the computer maintains a second sort of "database" of memory known as the heap. The heap can store variably-sized values, but is slower than the stack.
But how does the computer know when a value should be found on the stack or the heap?
Pointers. When we created myArray (by the way, that new keyword is what shows it's coming from the heap), the computer stored it in a location on the heap, but then stored a reference to that location on the stack.
This allows us to store both primitives and non-primitives in a coherent, efficient way.
As a side note, especially with Java, some people might be confused by arrays (not Lists). When we declare them, they look like they should be primitives:
int[] myArr = {0, 1, 2};
They're of fixed size, so they should be allocated on the stack, right? Well, no.
Consider this:
public static void myFunc(int x) {
int[] myArr = new int[x];
}
Because the stack frames are calculated at compile-time, myArr cannot be allocated on the stack, because we won't know how big it will be until runtime.
As it turns out, arrays are not primitives, they are just 'special' objects.
I just thought I should include that for completeness' sake.