Tornado Cash
The merkle root contains the users node. The contract doesn't need to have the latest version of the tree as the user will be present as a node once inserted.
When a user wants to withdraw funds from the contract, they must first prove ownership of a note. To do this, they reveal the commitment and the nullifier hash.
The snark prover submitted by the user contains root, a public input root and private inputs: secret, nullifier, pathElements and pathIndices.
The Proof computes two hashes. A commitment hash of the secret and the nullifier. Second a hash of just the nullifier, called nullifier hash. For an external observer, these two hashes seem uncorrelated
The contract checks that the root is one of the last 100? merkle roots and whether the nullifier hash is unique.
It verifies the nullifier hash against a list of previously spent nullifiers. If the nullifier hash is found in the list, the transaction is rejected as a double spend attempt.
If the nullifier hash is not found in the list, the transaction is added to the blockchain the contract releases the funds to the withdrawer. This mechanism of using nullifier to prevent double spend was first adopted by Zcash.