At the protocol level, Bitcoin has no concept of “balances” or “addresses”, and their common interpretation isn’t relevant during verification.
Every transaction spends “coins” and reforges them into new “coins” (called UTXOs). Every UTXO has a value (in number of satoshis) and an locking script. UTXOs are always spent in their entirety. If you only have a 1 BTC UTXO and want to send 0.1 BTC to someone, your wallet will under the hood create a transaction that has one input (your 1 BTC UTXO) and two outputs (one 0.1 BTC UTXO with an locking script created from the receiver’s address, and one 0.9 BTC UTXO with an locking script you can unlock yourself). From a protocol perspective, there is nothing special about this UTXO that goes back to yourself (called a “change output”) – it’s just another UTXO.
Transaction explicitly identify which previous coins they’re spending, based on the txid that created them, not based on the locking script. So it doesn’t matter what the sum of the values of UTXOs with a particular locking script is; coins are spent when they’re mentioned as an input in another transaction. That means the only thing that matters is:
- Do the input UTXOs exist?
- Is the sum of the input UTXO values (whether they have the same locking script or not) not less than the sum of the values of the newly created UTXOs.
Now, sites like blockexplorers do show balances. They’re a higher-level – and misleading – abstraction. They do this by simply maintaining an index per address of all transactions that have the script corresponding to that address as locking script, and all transactions that spends such outputs.
Wallets for privacy reasons construct new addresses all the time (typically one per incoming transaction), as there is no cost to creating more addresses. Looking at it from a per-address perspective is therefore misleading, and has caused people to lose money (when they think “X is my address, I only need its private key” – you need the keys to all addresses, even change addresses, your wallet uses).