There are two ways to defining geometries:
Non-Indexed
"vertices": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, ... ],
"normals": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, ... ]
In this mode every triangle position is as defined and you can't reuse data.
triangle 0: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
Indexed
"indices": [ 0, 1, 2, ... ],
"vertices": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, ... ],
"normals": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, ... ]
In this mode, indices define the order of the data. The first triangle is using indices 0, 1, and 2. These indices will be used to fetch the vertices and normals data:
triangle 0: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
The main benefit of indexed is the possibility of reusing data and uploading less data to the GPU:
"indices": [ 0, 0, 0, ... ],
"vertices": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, ... ]
triangle 0: [ 0, 1, 2, 0, 1, 2, 0, 1, 2 ]
As per offsets...
With offsets you can render specific ranges of your geometry. Instead of drawing from triangle 0 to triangle.length, you can draw from triangle 200 to triangle 400.