In most cases, loops work best with the ``loop`` keyword instead of ``with_X`` style loops. The ``loop`` syntax is usually best expressed using filters instead of more complex use of ``query`` or ``lookup``.

These examples show how to convert many common ``with_`` style loops to ``loop`` and filters.

with_list
---------

``with_list`` is directly replaced by ``loop``.

.. code-block:: yaml+jinja

    - name: with_list
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_list:
        - one
        - two

    - name: with_list -> loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - one
        - two

with_items
----------

``with_items`` is replaced by ``loop`` and the ``flatten`` filter.

.. code-block:: yaml+jinja

    - name: with_items
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_items: "{{ items }}"

    - name: with_items -> loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ items|flatten(levels=1) }}"

with_indexed_items
------------------

``with_indexed_items`` is replaced by ``loop``, the ``flatten`` filter and ``loop_control.index_var``.

.. code-block:: yaml+jinja

    - name: with_indexed_items
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      with_indexed_items: "{{ items }}"

    - name: with_indexed_items -> loop
      ansible.builtin.debug:
        msg: "{{ index }} - {{ item }}"
      loop: "{{ items|flatten(levels=1) }}"
      loop_control:
        index_var: index

with_flattened
--------------

``with_flattened`` is replaced by ``loop`` and the ``flatten`` filter.

.. code-block:: yaml+jinja

    - name: with_flattened
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_flattened: "{{ items }}"

    - name: with_flattened -> loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ items|flatten }}"

with_together
-------------

``with_together`` is replaced by ``loop`` and the ``zip`` filter.

.. code-block:: yaml+jinja

    - name: with_together
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      with_together:
        - "{{ list_one }}"
        - "{{ list_two }}"

    - name: with_together -> loop
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      loop: "{{ list_one|zip(list_two)|list }}"

Another example with complex data

.. code-block:: yaml+jinja

    - name: with_together -> loop
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
      loop: "{{ data[0]|zip(*data[1:])|list }}"
      vars:
        data:
          - ['a', 'b', 'c']
          - ['d', 'e', 'f']
          - ['g', 'h', 'i']

with_dict
---------

``with_dict`` can be substituted by ``loop`` and either the ``dictsort`` or ``dict2items`` filters.

.. code-block:: yaml+jinja

    - name: with_dict
      ansible.builtin.debug:
        msg: "{{ item.key }} - {{ item.value }}"
      with_dict: "{{ dictionary }}"

    - name: with_dict -> loop (option 1)
      ansible.builtin.debug:
        msg: "{{ item.key }} - {{ item.value }}"
      loop: "{{ dictionary|dict2items }}"

    - name: with_dict -> loop (option 2)
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      loop: "{{ dictionary|dictsort }}"

with_sequence
-------------

``with_sequence`` is replaced by ``loop`` and the ``range`` function, and potentially the ``format`` filter.

.. code-block:: yaml+jinja

    - name: with_sequence
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_sequence: start=0 end=4 stride=2 format=testuser%02x

    - name: with_sequence -> loop
      ansible.builtin.debug:
        msg: "{{ 'testuser%02x' | format(item) }}"
      # range is exclusive of the end point
      loop: "{{ range(0, 4 + 1, 2)|list }}"

with_subelements
----------------

``with_subelements`` is replaced by ``loop`` and the ``subelements`` filter.

.. code-block:: yaml+jinja

    - name: with_subelements
      ansible.builtin.debug:
        msg: "{{ item.0.name }} - {{ item.1 }}"
      with_subelements:
        - "{{ users }}"
        - mysql.hosts

    - name: with_subelements -> loop
      ansible.builtin.debug:
        msg: "{{ item.0.name }} - {{ item.1 }}"
      loop: "{{ users|subelements('mysql.hosts') }}"

with_nested/with_cartesian
--------------------------

``with_nested`` and ``with_cartesian`` are replaced by loop and the ``product`` filter.

.. code-block:: yaml+jinja

    - name: with_nested
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      with_nested:
        - "{{ list_one }}"
        - "{{ list_two }}"

    - name: with_nested -> loop
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      loop: "{{ list_one|product(list_two)|list }}"

with_random_choice
------------------

``with_random_choice`` is replaced by just use of the ``random`` filter, without need of ``loop``.

.. code-block:: yaml+jinja

    - name: with_random_choice
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_random_choice: "{{ my_list }}"

    - name: with_random_choice -> loop (No loop is needed here)
      ansible.builtin.debug:
        msg: "{{ my_list|random }}"
      tags: random