True but one must remember that index(0) is not enumerated by ipair() which only treats indexes that are part of an uninterrupted ordinal "sequence" from 1 to N.
And Lua implementations have normally optimized the table storage for sequences (so that their keys are not stored) but not necessarily for key 0 (which may still be accessed via the hash, containing all or most keys that are not part of a "sequence" except possibly a few integer keys < 1 or > #t, which may still remain in the internal indexed array for performance reasons, to avoid resizing or moving this array too frequently, when it can keep some unused slots assigned with a "nil" value; that internal array may then be "a bit" larger in size than #t, and used if preferably before adding/removing keys in the separate hash; unallocating large ranges of unused slots at end of the array may be done conditionally, only above some volume, and while still keeping that array allocated within an aligned size in memory to make better use of what the native memory allocator may have reserved).
Such optimization of tables for their "sequence" part offers many performance benefits (with a very modest complexity in the internal code for get/set accessors), it will almost always save memory because keys don't have to be stored and are just implicit positions in the array, and will avoid also the hashing function (which is not necessarily trivial, even for integer keys), the management of the collision lists for each slot in the hashtable, and multiple tests in a loop. All that is needed is to check the key type (is it an integer?) and value (is it in the min..max range of the optimized part which contains all the "sequence"?).