**Generally speaking**, both are used like memory aliases. But, a variable more directly aliases a memory offset at compile time, and a key informs a memory mapping algorithm at runtime.

**Conceptually**, the primary difference is one of **scope**: Whether the alias operates at compile time versus runtime.

**Functionally**, the primary difference is one of **runtime mutability** (of all such aliases): Whether the collection of aliases can be changed at runtime. You can't compile your code and add `Int32 xyz` at runtime. But, you *can* add *key* `xyz` to a collection. (With some interpreted languages being an exception.)

**Practically**, the primary difference is **lookup strategy and efficiency**: Variables hold memory offsets. Using them is fast. Keys are piped through a lookup algorithm that, comparatively speaking, *eventually* determines a memory offset and returns its contents.

Lots of work is done at runtime to make keys work. Variables are sort of *just there*.

<sup>This all assumes you're referring to keys in the context of a collection. You can certainly have key-value pairs that have nothing to do with collections or finding things in memory. But, the keys in that context are far enough removed *functionally* that ... well ... I'm just assuming the question isn't referring to that.</sup>