## TL;DR
Make the step definition's pattern match the feature file's step text exactly, and declare the DataTable as the last parameter of the step function. Most mismatches are a plural, article, or quoting difference between the `.feature` line and the pattern, or the table argument missing from the function signature.

## Problem
A scenario step passes a data table, but the run reports the step as undefined, or the step function receives the wrong arguments (the table arrives as undefined, or an arity error is raised).

## Steps
1. Copy the step text from the `.feature` file character for character and compare it with the step definition pattern. Fix plurals, articles, and punctuation first; they are the usual culprits.
   Expected: the pattern and the step text agree on every word.
2. In cucumber-js, declare the table as the final argument:
   ```js
   const { Given } = require('@cucumber/cucumber');

   Given('the following users exist:', function (dataTable) {
     const rows = dataTable.hashes();
     // rows is an array like [{ name: 'ann', role: 'admin' }]
   });
   ```
   Expected: `dataTable.hashes()` returns the rows instead of throwing.
3. In cucumber-ruby, use a block whose last parameter receives the table:
   ```ruby
   Given('the following users exist:') do |table|
     table.hashes.each do |row|
       create_user(name: row['name'], role: row['role'])
     end
   end
   ```
   Expected: `table.hashes` yields one hash per row.
4. Re-run with `--dry-run` to confirm every step is now defined, then run for real.
   Expected: no undefined steps; the scenario executes.

## When to use
- "Undefined step" on a step that clearly has a definition.
- DataTable arrives as undefined in the step function.
- Arity errors after adding a table to an existing step.

## When not to use
- DocString (triple-quoted text) steps: those pass a string, not a table; different handling.
- Steps with no table where only the pattern mismatches: fix the pattern, the table advice does not apply.
- Mixing cucumber expressions and regexes: pick one style per project.

## Tool compatibility
- cucumber-js 9.x through 11.x (`@cucumber/cucumber`); `dataTable.hashes()` is stable API.
- cucumber-ruby 8.x through 9.x; the block-last-parameter table convention is unchanged.

## Variant phrasings
### cucumber step definition not matching data table step
Check for a trailing colon difference: the feature step `Given the following users exist:` needs the colon in the pattern too.
### dataTable is undefined in cucumber-js step
The function signature is missing the final parameter; add `dataTable` last, after any capture-group params.

## Why it happens
Cucumber binds steps by matching the full step text against the definition pattern, then passes capture groups plus the table or DocString positionally. A one-word mismatch means no binding; a missing final parameter means the table has nowhere to land.

## Edge cases
- Tables under Scenario Outline steps: the table binds to the step while Examples fill the placeholders; both can coexist.
- Empty tables (header row only): `hashes()` returns an empty array; guard the loop if setup must run at least once.
- Trailing whitespace in the feature file step text is invisible but breaks matching; enable visible-whitespace in your editor.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_qNFG3lL_Fm9jXbNFIDtUpg
