In an Ansible playbook, what does the Jinja2 expression `{{ myvar | default('fallback') }}` do, and how do `default('fallback', true)` and `default(omit)` differ from it?
answer
- undefined is not the same as empty
- second argument widens it to falsy
- a magic value deletes the parameter
- zero and false are legitimate values
- is defined for branching, default for substituting
basics
~20 sThe default filter supplies a value only when the variable is undefined. Passing true as a second argument also replaces empty or otherwise falsy values. Passing the special omit value removes the parameter from the task entirely, so the module applies its own default.
solid answer
~40 s`default('fallback')` fires only on *undefined* — if `myvar` exists but is an empty string, `0` or `false`, you get that value, not the fallback. Adding a truthy second argument, `default('fallback', true)`, widens it to any falsy value, which is what you usually want for a variable that may arrive as an empty string from inventory. `default(omit)` is Ansible-specific rather than plain Jinja: `omit` is a magic value that makes the whole parameter disappear from the module call, so the module's own default applies instead of you guessing it. That is the correct way to make an optional module argument optional — passing an empty string instead would set the owner or group to `''` rather than leaving it alone. For a check rather than a substitution, use the Jinja test `is defined`.
code
yaml · 14 lines- name: Optional parameters done right
ansible.builtin.file:
path: /srv/app
state: directory
owner: "{{ app_owner | default(omit) }}"
mode: "{{ app_mode | default(omit) }}"
- name: Empty string from inventory still needs a fallback
ansible.builtin.debug:
msg:
- "undefined only: {{ app_port | default(8080) }}"
- "falsy too: {{ app_port | default(8080, true) }}"
vars:
app_port: ""go deeper
Know that default() supplies a value only when the variable does not exist, and that a variable set to an empty string still counts as existing. Be able to write a safe reference in a template.
Explain the three cases cleanly — undefined, falsy, absent — and say why omit is the correct way to make a module parameter optional instead of passing an empty string.
Show judgment about where defaults belong: role defaults/main.yml declares the interface, inline default() covers genuinely optional values, and boolean mode is dangerous where 0 or false is meaningful.
Own the convention across the estate: which values must be asserted rather than defaulted, so a missing region or account id fails the run loudly instead of quietly rendering a plausible wrong config.
## The problem Ansible variables are frequently optional: a role ships without an opinion, and inventory may or may not supply one. If a template or a task references a name that was never defined, the run fails — by default `error_on_undefined_vars` is true in `ansible.cfg`, and an undefined variable in a task is a fatal error for that host. The `default` filter is the standard way to make a reference safe. ## Plain `default(value)` — undefined only ```yaml - ansible.builtin.debug: msg: "{{ app_port | default(8080) }}" ``` If `app_port` is not defined anywhere, this renders `8080`. If it *is* defined, whatever it holds is used — including an empty string, `0`, `false`, or an empty list. This surprises people constantly, because YAML makes it very easy to produce an empty value: ```yaml app_port: # this is defined, and its value is null (None) ``` That key is defined with a `None` value, so `default(8080)` does **not** fire and the module receives `None`. ## `default(value, true)` — any falsy value The second positional argument is the *boolean* mode from Jinja: when truthy, the filter substitutes whenever the input is falsy, not merely undefined. ```yaml "{{ app_port | default(8080, true) }}" ``` Now an empty string, `null`, `0`, `false` and an empty list all yield `8080`. This is the right choice for values arriving from a templated inventory, a CI environment variable, or a `vars_prompt` the operator may have skipped. It is the *wrong* choice when `0` or `false` is a legitimate value you must be able to set — `replicas: 0` silently becoming the default is a nastier bug than a crash. ## `default(omit)` — remove the parameter `omit` is a special Ansible variable, not a Jinja concept. When a module parameter's value ends up being `omit`, Ansible strips that parameter from the task before invoking the module: ```yaml - ansible.builtin.file: path: /srv/app state: directory owner: "{{ app_owner | default(omit) }}" mode: "{{ app_mode | default(omit) }}" ``` If `app_owner` is undefined, the `owner` parameter is not sent at all, so the `file` module leaves ownership as it found it. Compare the alternatives: `default('')` would send an empty owner (an error or an unwanted change), and hardcoding `default('root')` bakes in an opinion the module already has a better answer for. `omit` is therefore the idiomatic way to write a role with genuinely optional pass-through parameters, and it works inside loops too — each iteration is evaluated separately. One limit worth knowing: `omit` only has this effect where a module parameter is being built. Inside a template file or a `debug` message it has no special meaning and renders as an internal placeholder string. ## Checking rather than substituting When the branch matters more than the value, use Jinja tests in a `when`: ```yaml - name: Only when a certificate was supplied ansible.builtin.copy: src: "{{ tls_cert_path }}" dest: /etc/ssl/app.pem when: tls_cert_path is defined ``` `is defined` / `is undefined` test existence; `is none` tests for a null value; `| length > 0` tests emptiness. Note that `when` expressions are already Jinja, so they are written bare, without `{{ }}`. ## Chaining and readability `default` chains left to right, which gives a clean fallback ladder: ```yaml "{{ host_timeout | default(group_timeout) | default(30) }}" ``` It is tempting to write these everywhere, but a role littered with inline defaults is harder to reason about than one that declares its defaults once in `defaults/main.yml` — the lowest precedence source, designed exactly for this. Use `defaults/main.yml` for the role's stated interface, and reserve inline `default()` for values that are genuinely optional at the point of use, for `omit` cases, and for defensive access to facts that may not exist on every platform (`ansible_facts.get('lsb', {})`-style guards). ## Interview framing The distinction interviewers listen for is undefined versus falsy versus absent: `default(x)` covers undefined, `default(x, true)` covers falsy, `default(omit)` covers "do not send this at all". A candidate who knows only the first will eventually ship a task that sets a permission to an empty string.
- Why is default(omit) preferred over default('') for an optional owner or group parameter?Because they mean different things to the module. `default('')` still sends the parameter, with an empty value, which is either rejected or applied as a change. `omit` removes the parameter from the task entirely, so the module falls back to its own documented default and leaves the existing value untouched — which is what "optional" actually means.
- A variable is set in inventory but the key has no value. Does default() fire?No. `app_port:` with nothing after it is a defined key whose value is `null`, so plain `default(8080)` passes the `null` straight through. Either add the boolean second argument, `default(8080, true)`, or fix the inventory. This is the classic empty-versus-undefined trap.
- How would you fail the run deliberately when a required variable is missing, rather than defaulting it?Assert it early in the play with the `assert` module and a `myvar is defined` condition, or reference it with `| mandatory`. Failing loudly at the top of the play is far better than discovering three tasks later that a template rendered a blank hostname into a config file.
saying these in an interview costs you the question
- Thinks default() also replaces empty strings
- Uses default('') for an optional module parameter
- Believes omit is standard Jinja rather than Ansible-specific
- Uses default(false, true) where false is a valid value
- Confuses is defined with is none