いきなり言われてもピンとこないとおもうので、画像を用意しました。
const result = /a(b)?/.exec('a');
const { length } = result; // 2
const [matched, capture1] = result; // "a", undefined

groupsは名前付きキャプチャを使わなければundefinedなのでいいとして、result[1]がundefinedですね。
ただ、このぐらい単純な場合、書き方を変えればそれを回避できます。
const result = /a(b?)/.exec('a');
const { length } = result; // 2
const [matched, capture1] = result; // "a", ""

この挙動の違いは0文字をキャプチャしたグループとそもそも適用されなかったグループの違いです。
?や*、|によってスキップされてしまったグループをキャプチャすることは当然できません。
その場合、RegExp#exec()の配列の要素数は変わらず、undefinedが代入されます1。
この挙動はES5時点から変わらず仕様として定められています。
MDN(英語版)にも書いてない挙動2なので、RegExp#exec()やString#replace(regex, replaceFunction)でスキップするようなキャプチャグループは記述しないほうが混乱が減るでしょう。
-
英語圏だとブログとかではちらほら記述されているけど、日本語圏では皆無。というか陥りやすい罠なのにMDNに書かれてないのはどうなのか ↩