Caveat lector: this is a zeroth approximation, and wrong in many ways, but at least it's a zeroth approximation!
Using pseudocode, when you call [] or :, a new data structure is created. Here's how you might write the structure in an imperative language:
structure {
int constructor
ptr head
ptr tail
} list
When you write empty = [], that allocates a new list, and populates it this way:
empty = alloc(list)
empty.constructor = 0 // 0 means we used [] to construct this
The head and tail pointers are left uninitialized, because they aren't used. When you write not_empty = 3 : [4], that allocates a new list, and populates it this way:
// three is a pointer to 3
// four is a pointer to [4]
not_empty = alloc(list)
not_empty.constructor = 1 // 1 means we used : to construct this
not_empty.head = three
not_empty.tail = four
Now, when you pattern match on a list, that corresponds to checking the constructor field. So if you write, say:
case not_empty of
[] -> 7
x:xs -> 20 + x
Then what happens imperatively is something like this:
if not_empty.constructor == 0
return 7
elseif not_empty.constructor == 1
return 20 + dereference(not_empty.head)
endif
(Similarly, if you had mentioned xs, it would dereference the tail pointer.)
Between constructing list structures and pattern matching on them, you now know the basic building blocks used to build every function on lists! At the most basic level, those are the only two list-y things you can do.