Using item and loop_var in Ansible Loops
item as the default loop variable. Reserve loop_control.loop_var for cases where item would collide across loop boundaries, such as nested loops and include_tasks.Categories:
item as the default loop variable in Ansible loops. Introduce a custom
loop_control.loop_var only when item would clash across loop boundaries,
for example in include_tasks or nested loops.Problem
The default loop variable item is convenient and familiar, and in most cases
it is the best choice. Problems start when item is reused across loop
boundaries, for example when include_tasks passes loop data into another file
or when loops are nested.
In a verified test with ansible-core 2.21.4, Ansible emits a warning when an
included task starts another loop that also uses item:
[WARNING]: The variable 'item' is already in use.
You should set the `loop_var` value in the `loop_control` option for the task to
something else to avoid variable collisions and unexpected behavior.
Using a custom loop variable everywhere also adds noise. If a name such as
apache_vhost does not solve a real ambiguity, it only makes the task longer.
Context
In Ansible, item is the default variable name for loop and with_items.
That default is usually the best choice for short and local loops, including
loops over dictionaries and other objects.
A custom loop_control.loop_var is useful only when it prevents ambiguity.
This is most common in reusable
Ansible roles, included task files, and
nested loops. Merely iterating over an object is not by itself a reason to stop
using item.
For field access, consistency matters more than personal shorthand. The Ansible
documentation commonly shows dot notation such as item.name and
item.value.role, while Jinja documents both foo.bar and foo['bar'] as
valid syntax. In C2 Platform code, bracket notation is already used much more
often than dot notation, so this guideline prefers bracket notation for looped
objects to keep the codebase consistent.
Solution
- Use
loopinstead ofwith_itemsfor new code. - Use
itemby default. - Do not introduce a custom
loop_varif it adds no clear value. - Use
loop_control.loop_varwhenitembecomes ambiguous, especially in nested loops andinclude_taskspatterns. - When you introduce a custom loop variable, use it consistently in
label,when, and all expressions inside the task. - For looped dictionaries and objects, use bracket notation consistently,
such as
item['servername'], instead of mixing bracket and dot notation.
Benefits
- Keeps simple tasks short and aligned with normal Ansible style.
- Prevents confusion in included task files and nested loop scenarios.
- Avoids unnecessary custom names that add noise without improving the code.
- Reduces the chance of typos between similarly named variables such as
role_carsandrole_car.
Alternatives (Optional)
Using a custom loop_var for every loop creates unnecessary verbosity and is
not recommended. Using item for every loop is also not recommended, because in
reusable or more complex code it can hide what the loop value represents.
Examples and Implementation
Use item for simple values
Use item when the task is local and obvious.
- name: Install packages
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- git
- curl
- jq
Use loop_var for reusable include patterns
A custom loop variable is strongly preferred when the loop value is consumed in another task file.
- name: Restore
ansible.builtin.include_tasks: restore.yml
loop: "{{ oracle_database_restores }}"
loop_control:
loop_var: oracle_database_restore
label: "{{ oracle_database_restore['archive'] }}"
when: oracle_database_restore_enabled
This avoids item collisions across file boundaries.
Use item for object data when there is no collision
When the loop iterates over dictionaries or objects, item is still fine if
there is no ambiguity.
- name: Configure Apache vhosts
ansible.builtin.debug:
msg: "{{ item['servername'] }} -> {{ item['documentroot'] }}"
loop: "{{ apache_vhosts }}"
Here item['servername'] and item['documentroot'] are already clear enough.
Introducing apache_vhost adds little value and increases the chance of typos.
Avoid custom names that add no value
For short and obvious tasks, a custom loop variable only adds noise.
- name: Configure Apache vhosts
ansible.builtin.debug:
msg: "{{ apache_vhost['servername'] }} -> {{ apache_vhost['documentroot'] }}"
loop: "{{ apache_vhosts }}"
loop_control:
loop_var: apache_vhost
Here the custom name differs only by singular/plural from apache_vhosts,
which makes review and maintenance harder without solving a real problem.
Additional Information
- Loops — Ansible Community Documentation
- loop-var-prefix - Ansible Lint Documentation
- Variables — Jinja Template Designer Documentation
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.