Using item and loop_var in Ansible Loops

Use 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.

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

  1. Use loop instead of with_items for new code.
  2. Use item by default.
  3. Do not introduce a custom loop_var if it adds no clear value.
  4. Use loop_control.loop_var when item becomes ambiguous, especially in nested loops and include_tasks patterns.
  5. When you introduce a custom loop variable, use it consistently in label, when, and all expressions inside the task.
  6. 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_cars and role_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


Last modified September 10, 2026: guidelines coding C2-1630 C2-1628 C2-1629 (1f6a9b9)