Nice solution @MichaelAudire 
… and on the whole, your explanation is good. Here are some additional comments …
Yes … it was a temporary copy of the whole User
struct instance (stored in the mapping) which was initially stored in memory …
User memory user = users[id];
Then, the new balance was assigned to the balance property of this temporary copy, where it was only saved until the function had finished executing, at which point it was lost …
user.balance = balance;
When we define the data location of a local variable as storage
, this creates a storage pointer. This only points to (references) the value already saved in persistent storage which is assigned to it (in our case users[id]
); it doesn’t create or store a separate copy of this value, as it would if we defined the data location as memory
. A storage pointer is like a temporary “bridge” during execution of the function to wherever in the contract state it points to (in our case a specific User
struct instance in the mapping).
Any value which is assigned to a property of the local “pointer” (user
) will effectively update that same property of the specific User
instance in the mapping which is referenced by the pointer. This enables a specific user’s balance (stored persistently in the mapping) to be reassigned with the new balance
(input into the function), before our “bridge” disappears when the function finishes executing.
user.balance = balance;
And because …
… the user’s new balance
will also be stored persistently in the mapping, until it is updated again.
Just let me know if you have any questions 