TeachMeBitcoin

OP_ENDIF - Closing a Conditional Block

From TeachMeBitcoin, the free encyclopedia Reading time: 2 min

4. OP_ENDIF — Closing a Conditional Block

Overview

OP_ENDIF marks the end of a conditional block opened by OP_IF or OP_NOTIF. It does not interact with the stack at all — its sole purpose is structural. Every OP_IF and OP_NOTIF must be matched with exactly one OP_ENDIF.

Opcode Reference

Opcode:     OP_ENDIF
Hex:        0x68
Byte value: 104 (decimal)
Category:   Flow Control

Role in Script Parsing

Before execution even begins, Bitcoin nodes parse the script to verify structural integrity. This pre-execution check ensures:

  1. Every OP_IF/OP_NOTIF has a matching OP_ENDIF.

  2. Every OP_ELSE has a preceding OP_IF/OP_NOTIF.

  3. No OP_ENDIF appears without a preceding open conditional.

  4. Nesting depth does not exceed implementation limits.

; Pre-execution parse check (pseudocode):
depth = 0
for opcode in script:
  if opcode in (OP_IF, OP_NOTIF):
    depth += 1
  elif opcode == OP_ENDIF:
    depth -= 1
    if depth < 0:
      return SCRIPT_INVALID   ; unmatched OP_ENDIF
if depth != 0:
  return SCRIPT_INVALID       ; unclosed OP_IF

OP_ENDIF Does Not Touch the Stack

This is one of the most important properties of OP_ENDIF: it is a no-op with respect to the stack. Its only effect is to close the interpreter's conditional tracking state.

Stack before OP_ENDIF: [ 0x01 0x02 ]
OP_ENDIF
Stack after OP_ENDIF:  [ 0x01 0x02 ]  ; unchanged

Maximum Nesting Depth

The Bitcoin Core reference implementation enforces a maximum conditional nesting depth (the MAX_OPS_PER_SCRIPT and related limits apply indirectly). In practice, deeply nested scripts become both difficult to audit and expensive in terms of witness data. Tapscript was designed to allow more expressive scripts through Taproot's MAST (Merkelized Abstract Syntax Tree) structure rather than deep nesting.

OP_ENDIF in Tapscript

In Tapscript (BIP 342), OP_ENDIF behaves identically to its legacy counterpart. The key difference is that in Tapscript, each spend reveals only one script leaf from a Merkle tree, so the need for deeply nested conditionals is reduced — different spending paths can live in different Taproot leaves.

; Tapscript leaf A (Alice's path):
<alice_pubkey> OP_CHECKSIG

; Tapscript leaf B (Bob's path with timelock):
<locktime> OP_CHECKSEQUENCEVERIFY OP_DROP
<bob_pubkey> OP_CHECKSIG

; No OP_IF needed — paths are separate leaves in the MAST
☕ Help support TeachMeBitcoin

TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!