Kevin Lu is correct, but here is some additional context to why it works the way it does.
The ${{ <expression> }} syntax is only evaluated at template compile time.  This means that any user variables will not yet have been initialized. If you attempt to check their value, it will always return an empty value. While the ${{ <expression> }} syntax is handy to conditionally output template lines for processing, user variable values will not have been set limiting what you can do with the if.
The $[ <expression> ] syntax is evaluated at run time. Variables will have been initialized, however you can't directly use the if syntax to conditionally output a different variable value directly. You can use a cleaver hack however, as documented by Simon Alling's answer in this post.
Reference: https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions
This very simple YAML Pipeline illustrates the concept:
stages:
- stage: TestVarsCompile
  variables:
    - name: state_prefix
      # $subdirectory is not defined
      ${{ if eq(variables['subdirectory'], '') }}:
       value: 'subdirectory-not-set'
      # $subdirectory is defined 
      ${{ if ne(variables['subdirectory'], '') }}: 
       value: 'subdirectory-set'
  jobs:
   - job:
     steps:
     - checkout: none
     - script: |
        echo $(subdirectory)
        echo $(state_prefix)
- stage: TestVarsRuntime
  variables:
   state_prefix: $[
        replace(
          replace(
            eq(variables['subdirectory'], ''),
            True,
            'sub-directory-not-set'
          ),
          False,
          'sub-directory-set'
        )
      ]
  jobs:
   - job:
     steps:
     - checkout: none
     - script: |
        echo $(subdirectory)
        echo $(state_prefix)
The TestVarsCompile stage output always returns the following output regardless if the subdirectory variable is set or not:
some value
subdirectory-not-set
The TestVarsRuntime stage output will return the following if the subdirectory variable is set:
some value
subdirectory-set