IOTA uses the UTXO model to represent transactions. It means that the balance of a given address is defined as:
balance of address ADR = (sum of all "inputs" to ADR) - (sum of all "outputs" tx from ADR)
In practice, IOTA network make snapshots. After a snapshot: transaction history is lost (or at least the fullnode don't need to keep it in it's database). Instead of the full history: the balance of every address at snapshot time is the new starting point. Every address with non-zero balance is then seen as a genesis address. Therefore the balance of an address is :
balance of address ADR =
(initial amount on ADR at snapshot time)
+ (sum of all "inputs" to ADR)
- (sum of all "outputs" tx from ADR)
Be sure to understand that a snapshot (from a coding perspective) is as simple as a huge Map (i.e. a set of key-value pairs) where keys are the addresses, and values are the amount of iota on the address.
Implementation details can be found in code but, as far as I understand, the idea is to update the snapshot (locally on the full node) by applying all transactions approved by a milestone every time a milestone is received.
So getting the balance of an address is as simple as looking into the updated snapshot
(of course this is done on a fullnode, the light-wallet simply use the GetBalances API to request this data)