there are 2 different ways of using a vertex array:

provide an array with vertex data and use for instance glDrawArrays(GL_QUADS, 0, vertex_number). the driver will take 12 float values from the array (4 vertices * 3 coordinates per vertex) and draw a quad from them; then the next 12 float values and draw another quad; and so on. since a regular mesh usually has 4 quads connected to a vertex, the same vertex will be in the array 4 times.

provide a vertex array, in which each vertex occurs only once, then use glDrawElements. this function needs a pointer to an index array, in which the connectivity between the vertices is defined. example:
float vertex_array[8][3] = { { 1., 1., 1. }, { 1., 1., 1. }, { 1., 1., 1. }, { 1., 1., 1. },
{ 1., 1., 1. }, { 1., 1., 1. }, { 1., 1., 1. }, { 1., 1., 1. } };
short index_array[6][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 0, 1, 5, 4 }, { 1, 2, 6, 5 }, { 2, 3, 7, 6 }, { 3, 0, 4, 7 } };
the vertices are a cube’s corner points; they are numbered from 0 to 7 (internally, by the driver). the index array defines the 6 faces, referring to the nodes’ position in the vertex array.
one advantage of an index array is that vertex array + index array need less memory. in this example: 8 vertices * 3 coordinates * sizeof(float) + 6 faces * 4 indices * sizeof(short) = 96 bytes + 48 bytes = 144 bytes.
if you draw the cube with a “normal” vertex array, you need: 6 faces * 4 vertices * 3 coordinates * sizeof(float) = 288 bytes.
another “plus” for the index array is the fact that it may be possible that if a quad is drawn, 2 of its vertices do not need to be transformed by the graphics card, because they were used before and are still in the cache.