All programs work with information. To the programmer, data takes many forms: a list, a table, an image, an essay, an album. To the computer, it all looks basically alike: it’s just a bunch of bytes in memory. Imagine a single column of a spreadsheet, each of the cells filled with a number from 0 to 255. All your stuff is scattered across that column, along with bookkeeping to track what lives where. And every interaction with the computer goes through that memory. Pressed a key on your keyboard? Stick a 97 in cell 5,342,387,264, please.
The basic bookkeeping is handled by a program called a “malloc” (for memory allocator). Malloc marks parts of the column as available or in use (using more entries in the same column!). If you need, say, 10 bytes of scratch space for something, call ptr = malloc(10), and receive a pointer to those ten bytes – an index into the column. If the pointer is row 1000, you may put whatever you please into cells 1000 to 1009 and read them back later. When you’re done, call free(ptr) and that region can be re-used somewhere else.
In a low level language like C, this is all you get. It’s still pretty far from dictionaries and tables, and if you want those things you have to build them up yourself using pointers that point to other pointers. High level languages hide those details: a Python list conceptually contains other Python objects, not pointers telling you how to find those objects, even if that’s how the list works internally. Allocating memory for objects is pretty easy, we can just call malloc during creation. The hard part is knowing when to free that memory: you don’t tell Python “hey I’m done with this list”, you just stop using it at some point, and someone has to clean up after you.