This is the tutorial that separates C++ from most of the languages people learn before it. In Java, Python and JavaScript, memory just sort of takes care of itself in the background. In C++, you're handed the keys, and with them, the responsibility.

The stack: fast, automatic, and temporary

void doSomething() {
    int x = 5;
    std::string name = "Sam";
} // x and name are automatically destroyed right here

Any variable declared normally, without anything special, lives on the stack. The moment the function it belongs to finishes, that memory is automatically cleaned up. You never have to think about freeing it yourself — this is the easy, safe default, and honestly it's what you should reach for whenever it fits your needs.

The heap: manual, flexible, and your responsibility

int* number = new int(5);
std::cout << *number;  // 5
delete number;           // you MUST do this yourself

new allocates memory on the heap, a separate region that survives beyond the function that created it, and hands you back a pointer to it. Unlike the stack, nothing cleans this up automatically. If you forget to call delete, that memory stays reserved for as long as your program keeps running, even though nothing can reach it anymore. This is a memory leak, and in a long-running program, a steady trickle of these can eventually eat up all the memory on a machine.

Why would you ever use the heap, then?

The stack is limited in size and everything on it disappears the moment its function returns. Sometimes you genuinely need data to outlive the function that created it, or you need an amount of memory that's only known while the program is actually running, not something you can hardcode ahead of time. That's exactly what the heap, accessed through new, is for.

The rule that saves you from most of the pain

Every single new needs exactly one matching delete. Not zero, and not two — exactly one. Calling delete twice on the same pointer, or using a pointer after it has already been deleted, causes undefined behaviour, which is a polite way of saying "anything could happen, including your program crashing in a way that's very hard to trace back."

Modern C++ has a much better answer

#include <memory>

std::unique_ptr<int> number = std::make_unique<int>(5);
// no delete needed — it cleans itself up automatically

Smart pointers like unique_ptr wrap up a heap allocation and automatically delete it the moment nothing is using it anymore, giving you the flexibility of the heap without manually tracking every delete yourself. Modern C++ code leans on these heavily, and honestly, if you're writing new C++ today, reaching for a smart pointer instead of a raw new is usually the right instinct.