In Ansible, definition of variable precedence is often overlooked. Despite what I wrote in my previous blog (Use Ansible like a Programming Language), Ansible is not exactly a programming language. There is a difference, especially on the way variables are managed.
At the beginning, as I am used to scripting, I was a bit confused by the way Ansible is handling variables.
For example, let’s say you have a string variable defined in group_vars/all.yml:

---
MyVar: ‘all.yml’

In same file, I define another variable using that variable:

WhereIsMyVariableEvaluated: ‘MyVar is coming from {{ MyVar }}’

I initially expected WhereIsMyVariableEvaluated variable to always be equal to ‘MyVar is coming from all.yml’ if all steps are evaluated in sequence. However, it might not depending on MyVar value override. This is variable precedence.
Thus, Ansible documentation has is a nice long list of priorities (from low to high priority):

  1. command line values (for example, -u my_user, these are not variables)
  2. role defaults (defined in role/defaults/main.yml)
  3. inventory file or script group vars
  4. inventory group_vars/all
  5. playbook group_vars/all
  6. inventory group_vars/*
  7. playbook group_vars/*
  8. inventory file or script host vars
  9. inventory host_vars/*
  10. playbook host_vars/*
  11. host facts / cached set_facts
  12. play vars
  13. play vars_prompt
  14. play vars_files
  15. role vars (defined in role/vars/main.yml)
  16. block vars (only for tasks in block)
  17. task vars (only for the task)
  18. include_vars
  19. set_facts / registered vars
  20. role (and include_role) params
  21. include params
  22. extra vars (for example, -e “user=my_user”)(always win precedence)

OK, but then what does it mean?

Run a simple playbook

First, I create a playbook with this simple task that will display WhereIsMyVariableEvaluated variable content:

---
- name: Test playbook
  hosts: localhost
  tasks:
    - name: Print WhereIsMyVariableEvaluated
      ansible.builtin.debug:
        msg: '{{ WhereIsMyVariableEvaluated }}'

Secondly, I have setup a WSL (Windows Subsystem Linux) Ubuntu machine with Ansible package installed and to run it, as it does not imply any server modification, I am using “-c local” option.

$ ansible-playbook playbook.yml -c local

PLAY [Test playbook] **********************************************************************************

TASK [Print WhereIsMyVariableEvaluated] ***************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "MyVar is coming from all.yml"
}

PLAY RECAP ********************************************************************************************
localhost          : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Until now, nothing unexpected.
Afterward, we can try to define MyVar in host_vars/localhost.yml (priority 10):

PLAY [Test playbook] **********************************************************************************

TASK [Print WhereIsMyVariableEvaluated] ***************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "MyVar is coming from localhost.yml"
}

Next, defining MyVar from within playbook itself, adding lines 4 and 5:

---
- name: Test playbook
  hosts: localhost
  vars:
    MyVar: 'playbook'
  tasks:
    - name: Print WhereIsMyVariableEvaluated
      ansible.builtin.debug:
        msg: '{{ WhereIsMyVariableEvaluated }}'

As a result, output of playbook run:

...
TASK [Print WhereIsMyVariableEvaluated] ********************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "MyVar is coming from playbook"
}
...

Finally, I can run playbook with an argument (highest priority (22)):

$ ansible-playbook playbook.yml -c local -e "MyVar=CLI"
...
TASK [Print WhereIsMyVariableEvaluated] ********************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "MyVar is coming from CLI"
}
...

All these modifications were done by adding only, meaning nothing was removed from any yml file where MyVar was defined. This means previous definitions remained, but still highest priority is chosen by Ansible.

Conclusion

In short, WhereIsMyVariableEvaluated uses MyVar which was defined more recently. This mechanism allows to define default values for variable and overload them when required for specific hosts or groups while running a playbook.
I hope this have helped you to better understand and feel how Ansible variable precedence works.