In a Karate `Scenario Outline` step, what is the difference between writing `<name>` and `'#(name)'`, and which one preserves the column's type?
answer
- One rewrites text, one evaluates a value
- They resolve at two different moments
- Only one of them survives into the step
- Quoting is your job with the bracket form
- Type hints touch only one of the two
basics
~20 sIn 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 sThey 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 linesFeature: 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
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.
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.
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.
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