skip to content

In a Postman collection, how does a token like {{host-{{env}}}} resolve, and what may a name never contain?

level: middleimportance: should knowfreq 42%

answer

  1. The capture group excludes both brace characters
  2. The inner token is the only match
  3. Substitution repeats over its own output
  4. A named ceiling stops a circular chain

basics

~20 s

Innermost first. The extraction pattern excludes braces from a captured name, so only the inner token matches on the first pass; substitution then repeats and the composed name resolves later. A name may never contain a brace.

solid answer

~50 s

The pattern that finds tokens captures **any run of characters that are not braces** between an opening double brace and a closing one. In `{{host-{{env}}}}` the only span that satisfies that is the inner `{{env}}`, so the first pass replaces it and leaves something like `{{host-prod}}` behind. Substitution is not a single pass: the SDK loops, re-running the same replace over its own output while the previous pass changed something and the substitution count is still under `Substitutor.VARS_SUBREPLACE_LIMIT`. The second pass now sees `{{host-prod}}` as an ordinary token and resolves it. The same loop is what lets a variable whose *value* is itself `{{other}}` resolve. The cost of that pattern is that a variable whose name genuinely contains `{` or `}` can never be reached — `{{be{t}a}}` is not matched at all and arrives literal.

code

javascript · 7 lines
javascript
const REGEX_EXTRACT_VARS = /{{([^{}]*?)}}/g;

'{{beta-{{gamma}}}}'.match(REGEX_EXTRACT_VARS);
// ['{{gamma}}']

'{{be{t}a}}'.match(REGEX_EXTRACT_VARS);
// null

go deeper

for a junior

Recall that tokens can nest and that the inner one is handled first, and that a variable name should be kept to plain characters — braces in a name make it unreachable.

for a middle

Explain the two halves together: the capture group forbids braces inside a token, which is why nesting works inner-first, and substitution repeats over its own output so the composed name resolves on a later pass.

for a senior

Show judgment about depth — recognise a leftover token after a nested chain as a failed link in the chain rather than a syntax error, and know that a circular chain ends quietly at the ceiling instead of failing loudly.

for a principal

Own the convention: composed names are powerful but push configuration into string arithmetic that nothing validates, so decide deliberately how much indirection a shared collection is allowed to carry.

## The pattern that cannot see braces Postman's SDK extracts tokens with `Substitutor.REGEX_EXTRACT_VARS`, and the important part of it is the capture group: it accepts **any run of characters that are not `{` and not `}`**, sitting between an opening double brace and a closing one. Two consequences follow, and both are the answer to this question. First, **a token can never contain another token**. Applied to `{{host-{{env}}}}`, the pattern cannot match the outer span, because the characters inside it include braces. The only substring that satisfies the pattern is the inner `{{env}}`. So the innermost token is always the one that matches, and it matches first — not by any explicit ordering rule, but because it is the only thing the pattern can see. Second, **a variable whose name genuinely contains a brace is unreachable**. This is a known limitation, called out as such in the SDK's own test suite: given a variable literally named `be{t}a`, the text `{{be{t}a}}` never matches and arrives at the server as written. ## One pass is not enough, so the SDK loops Because the inner token is replaced first, one pass leaves a *new* token behind. The SDK therefore runs substitution in a loop, re-running the same replace over its own output. Two counters drive it: the number of tokens replaced by the most recent pass, and the number of passes that changed anything. The loop repeats while the last pass replaced something **and** the running count is still below `Substitutor.VARS_SUBREPLACE_LIMIT`, the SDK's named ceiling on how many times it will try. Walk `{{alpha}}`, where `alpha` holds `{{beta-{{gamma}}}}`, `gamma` holds `delta`, and `beta-delta` holds `epsilon`: 1. **Pass one** replaces `{{alpha}}` with its value, so the text becomes `{{beta-{{gamma}}}}`. 2. **Pass two** can only see `{{gamma}}`, and replaces it, giving `{{beta-delta}}`. 3. **Pass three** sees an ordinary token, resolves it, and the text becomes `epsilon`. 4. **Pass four** replaces nothing, so the loop ends. The same loop is what resolves the far more common shape: a variable whose **value** contains a token, such as a base URL built as `{{protocol}}://{{hostname}}`. There is nothing special about that case; it is the identical mechanism with the nesting one level up. ## What a name may and may not contain | Variable name | Reachable by a token? | Why | |---|---|---| | `alp/ha` | yes | a slash is an ordinary character to the pattern | | `api.host` | yes | a dot is ordinary too; the lookup is a plain keyed read | | `beta-delta` | yes | a hyphen is ordinary, and the name can also be composed by an inner token | | `be{t}a` | **no** | the capture excludes `{` and `}`, so nothing matches | | `_host_` with spaces around it | only if stored that way | the captured name is taken as written and nothing trims it | The practical reading is that **braces are the only forbidden characters**, and that the name is matched exactly — there is no normalisation step between capture and lookup. ## When the loop does not converge The ceiling exists for the cases that never settle. Consider four variables that chain in a circle: `alpha` holds `{{beta}}`, `beta` holds `{{gamma}}`, `gamma` holds `{{delta}}` and `delta` holds `{{beta}}`. Every pass finds a token and replaces it, so the "did anything change" counter never goes quiet; the loop simply runs until the substitution count reaches the limit and then stops. The SDK's own test pins the result: the text ends up as the literal `{{beta}}`. A token nothing can answer behaves the same way for the same reason: the replacer is still called on every pass, so the loop keeps going until the ceiling stops it, and the token is then left in place. Either way the outcome is the one that matters operationally — **the text is sent with a token still in it**, and no error is raised. ## Practical consequences - **Composed names work, and they are a real technique.** `{{host-{{env}}}}` with `env` set per environment is a legitimate way to pick one of several stored hosts. - **Deep chains are fragile.** Every level costs a pass, and the ceiling is finite; a chain that is long *and* circular ends as a literal token rather than as a diagnostic. - **Never put a brace in a variable name.** It is not merely awkward to type: no token can ever reach it. - **Do not expect trimming.** A stray space inside the braces changes the name being looked up. - **A leftover token after nesting is a chain problem, not a syntax problem.** Resolve the innermost name first, then work outwards; the pass that failed is the one whose composed name had no entry.

  • What happens when a chain of variables refers back to itself in a circle?
    Every pass finds a token and replaces it, so the loop never runs out of work and stops only when the substitution count reaches the SDK's ceiling. The text is then sent with a token still in it — the SDK's own test for a four-variable cycle expects the literal `{{beta}}` to survive. No error is raised, so a cycle looks exactly like a name nobody defined.
  • Does a variable whose value contains a token use a different mechanism from a composed name?
    No, it is the same loop. `{{base}}` whose value is `{{protocol}}://{{host}}` resolves because the pass that wrote the value is followed by another pass that sees the tokens it introduced. Composed names such as `{{host-{{env}}}}` differ only in that the inner token contributes part of a *name* rather than part of a value.

saying these in an interview costs you the question

  • Claims the outer token of a nested pair is matched first
  • Says substitution is a single pass over the text
  • Thinks any character may appear in a variable name
  • Assumes a circular chain raises an error or hangs the run
  • Believes nested tokens are a special syntax rather than repeated passes