Most descriptions of Bitcoin Script call it "Bitcoin's programming language" and then immediately compare it to general-purpose languages. This framing obscures the critical design point: Script doesn't compute results. It evaluates to true or false. Every valid script execution terminates with a non-empty stack whose top element is non-zero, or the spend fails. There is no return value, no state mutation, no side effects beyond the binary verdict.
This makes Script a predicate system implemented on a stack machine. The transaction's scriptSig (or witness data) provides inputs; the UTXO's scriptPubKey provides the predicate. The two are concatenated (conceptually — post-BIP 16 they're actually executed sequentially with a stack handoff to prevent certain malleability vectors) and evaluated left-to-right. Every opcode either pushes data onto the stack, manipulates existing stack elements, or performs a verification check that aborts execution on failure.
The stack holds byte vectors up to 520 bytes. Arithmetic opcodes interpret the top stack elements as little-endian signed integers, but only up to 4 bytes (though CScriptNum allows a 5th byte solely as a sign byte in edge cases). This 4-byte arithmetic limitation is one of the most commonly misunderstood constraints — it means Script cannot natively handle values larger than ±2³¹-1, which is why amounts in satoshis (up to ~2.1×10¹⁵) cannot be directly manipulated arithmetically within Script. Tapscript's OP_SUCCESS opcodes reserve space for future upgrades that could change this.