A full node has a local database of current IOTA balances (which gets initialized from the latest Snapshot and then updated by confirmed transactions). When the full node performs its validation of a transaction bundle, it will verify that the balances of the spending transactions do not exceed the amounts stored in its database. This is done sequentially so if there are double spends, only one can be validated.
When a light node will perform the PoW for 2 transactions, it will ask a full node to
getTransactionsToApprove. At that point, the full node will only return transactions that it could validate at that point too (i. e. only one incarnation of a double spend). So as a light node, you do not blindly approve transactions that cannot be valid. However, there is no guarantee that consensus (or the Coordinator) will pick the same transaction as your node did, so it can be that when ultimately validation your transaction later, it gets rejected as it approves an invalid transaction.