Each path of execution (read: token) has its own set of process variables. Requesting a variable is always done on a token. Process instances have a tree of tokens (see graph oriented programming). When requesting a variable without specifying a token, the default token is the root token.
The variable lookup is done recursively over the parents of the given token. The behaviour is similar to the scoping of variables in programming languages.
When a non-existing variable is set on a token, the variable is created on the root-token. This means that each variable has by default process scope. To make a variable token-local, you have to create it explicitely with:
ContextInstance.createVariable(String name, Object value, Token token);
Variable overloading means that each path of execution can have its own copy of a variable with the same name. They are treated as independant and hence can be of different types. Variable overloading can be interesting if you launch multiple concurrent paths of execution over the same transition. Then the only thing that distinguishes those paths of executions are their respective set of variables.
Variable overriding means that variables of nested paths of execution override variables in more global paths of execution. Generally, nested paths of execution relate to concurrency : the paths of execution between a fork and a join are children (nested) of the path of execution that arrived in the fork. For example, if you have a variable 'contact' in the process instance scope, you can override this variable in the nested paths of execution 'shipping' and 'billing'.
For more info on task instance variables, see the section called “Task instance variables”.