skip to content

In a Karate `Scenario Outline` step, what is the difference between writing `<name>` and `'#(name)'`, and which one preserves the column's type?

level: middleimportance: must knowfreq 52%

answer

  1. One rewrites text, one evaluates a value
  2. They resolve at two different moments
  3. Only one of them survives into the step
  4. Quoting is your job with the bracket form
  5. Type hints touch only one of the two

basics

~20 s

In Karate, <name> is a text substitution: the raw cell text is spliced into the step before it runs. '#(name)' is an embedded expression evaluated at run time against the bound variable, so the value keeps its type.

solid answer

~50 s

They are two different machines. `<name>` is a **plain text replace** that Karate performs on the scenario name, each of the scenario's steps, their doc strings and their step data tables *before* the scenario executes; whatever raw text sits in that cell is pasted in verbatim, so you normally have to quote it yourself as `'<name>'`, and a `!` type hint on the header changes nothing about it. `'#(name)'` is an **embedded expression**: at run time Karate evaluates `name` — the variable it auto-bound from that column — and drops the **actual value** in, so a number stays a number and an object stays an object. Use `#(...)` whenever the value lands in JSON, such as a request body or a `match` right-hand side, and `<...>` when you want it in the scenario name or in literal text.

code

gherkin · 13 lines
gherkin
Feature: placeholder versus embedded expression

Scenario Outline: <name> is <age>
* def spliced = '<age>'
* def embedded = age
* match spliced == '5'
* match embedded == 5
* def body = { who: '#(name)', years: '#(age)' }
* match body == { who: 'Bob', years: 5 }

Examples:
| name | age! |
| Bob  | 5    |

go deeper

for a junior

Recall the split: angle brackets paste raw text before the step runs, and you quote it yourself; the embedded form fetches the variable's real value while the step runs.

for a middle

Explain the two moments — substitution happens on the generated scenario's name, steps, doc strings and tables, while an embedded expression is resolved by the engine during the step.

for a senior

Recognise the symptom: a match failing with what looks like identical values on both sides is a placeholder used where a typed value was needed, or a missing type hint on the column.

for a principal

Set the convention once — embedded expressions inside anything JSON-shaped, placeholders only in text — so reviewers can spot the mistake without reconstructing which mechanism fired when.

## Two mechanisms, two different moments `<name>` and `#(name)` look like variations on one idea. They are not. | | `<name>` | `'#(name)'` | |---|---|---| | What it is | text substitution | embedded expression | | When it happens | before the scenario runs | while the step runs | | What it inserts | the **raw cell text** | the **evaluated value** | | Type preserved | no — it is characters | yes — number stays number | | Affected by a `!` hint | no | yes | | Works in the scenario name | yes | no | `<name>` is Cucumber-shaped: Karate walks the generated scenario and does a literal string replace of `<name>` across the scenario's **name**, each of its **steps' text**, their **doc strings** and any **step data table**. By the time the first step executes there is no placeholder left — the step's text simply *is* the substituted text. `#(name)` is Karate's own. It survives into execution and is resolved by the engine against the variable `name`, which Karate bound automatically from the column of the same name. ## Why the difference shows up in a request body ```gherkin Scenario Outline: create a cat Given url 'https://api.example.com' And path 'cats' And request { name: '#(name)', age: '#(age)' } When method post Then status 200 Examples: | name | age! | | Bob | 5 | ``` Here `'#(age)'` puts the **number** `5` into the JSON, because the `!` hint made the variable a number. Writing `{ age: '<age>' }` would put the **string** `'5'` there, because the placeholder splices the characters `5` and your own quotes wrap them. Writing `{ age: <age> }` happens to work — the characters `5` land unquoted and JavaScript parses them as a number — but it is fragile: it depends on the cell's text being valid syntax at that spot, and it breaks the moment a value contains a space, a comma, or a quote. The rule of thumb that survives review: **inside anything JSON-shaped, use `#(...)`; outside it, use `<...>`.** ## What `<name>` reaches, and what it does not - It reaches the **scenario name**, which is why a report can show `create a cat: Bob` per row. - It reaches the **outline's own steps**, their doc strings and their data tables. - It does **not** reach the `Background`. Background steps are shared across every scenario in the feature, so they are never rewritten per row. The *variables* still reach the `Background` — a `Background` step can say `* def x = name` — but a literal `<name>` there stays literal. - In a hand-written table, a blank cell splices the empty string, not a literal `<name>`. ## The whole-value rule, and where the lines differ An embedded expression is written as a **string that is entirely** `#(...)`. `'#(name)'` substitutes the value of `name`; `##(name)` does the same but removes the key from the JSON being built when the value is null, which pairs neatly with a blank `Examples:` cell. Whether `#(...)` also interpolates **inside a longer string** is version-sensitive. On the 1.5.2 line only the whole-value form is substituted — `'Hello #(name)!'` stays a literal string. The 2.x line added inline interpolation, so the same text becomes `Hello Bob!` there. Keep the whole-value form and the feature behaves the same on both; reach for interpolation and you have pinned yourself to 2.x. ## Choosing between them 1. **Values that go into JSON or XML** — request bodies, `match` right-hand sides, `def` of an object — take `#(...)`, so the type survives. 2. **Values that go into text** — the scenario name, a URL path segment, a doc string, a quoted literal — take `<...>`, because there is no type to preserve anyway. 3. **When the value came from a dynamic single-cell table**, prefer the variable or `#(...)`. Placeholders still work there, but the substituted text for an object value is its JSON form, and reasoning about that inside a step body is harder than just using the variable. 4. **Never mix them for the same value in the same step.** Two mechanisms resolving at two different moments in one line is where the confusing failures come from. ## The symptom to recognise A `match` that fails showing what looks like the same value on both sides — `5` against `5`, `true` against `true` — is nearly always a placeholder where an embedded expression belonged, or a missing `!` on the column. The message cannot show quotation marks around a type, so the two sides look identical. Assert `match __row == { age: '#number' }` once per row and the cause surfaces at the row instead of at the assertion.

  • In a Karate feature, does `<name>` get substituted inside `Background` steps?
    No. `Background` steps are shared by every scenario in the feature, so Karate never rewrites them per row — a literal `<name>` there stays literal. The row's variables do reach the `Background`, though, because they are bound before the first step runs, so `* def x = name` in a `Background` works fine.
  • What does `##(name)` do that `#(name)` does not, in a Karate step that builds JSON?
    The double-hash form removes the key from the object being built when the expression evaluates to null, instead of writing a null value. Paired with a blank `Examples:` cell — which binds null — that lets one outline drive both the field-present and the field-absent case from the same table without a second scenario.
  • A Karate step reads `And request { id: <id> }` and the `id` column has no type hint. What actually lands in the body?
    The raw cell text is pasted in unquoted, so if the cell holds `42` the body carries the number `42` — JavaScript parses the spliced characters. That is accidental, not by design: the variable `id` is still the string `'42'`, and the same step breaks the moment the cell holds anything that is not valid syntax at that position.

Angle brackets are a mail merge: the letter is rewritten before anyone reads it, and everything becomes characters. An embedded expression is a live cell reference: it is resolved when the line is read, and it keeps whatever type the value had.

saying these in an interview costs you the question

  • Says the two forms are interchangeable spellings of one mechanism
  • Thinks a ! type hint changes what an angle-bracket placeholder splices
  • Expects an embedded expression to work inside a scenario name
  • Assumes a placeholder is resolved from a variable while the step runs
  • Believes Background steps get their placeholders substituted per row
  • Relies on an embedded expression inside a longer string without checking the version