Skip to content

Inserting and deleting in an array

An array kept with its length and capacity becomes a reusable structure. Inserting or deleting in the middle shifts everything after it, so the cost depends on where.

Helps to know first
This topic

First, the picture

An array has no gaps to put a new number in. To insert, every cell after the spot moves one place along. Watch four of them make room for 15.

Making room in an array

Inserting 15 into five numbers shifts everything after it

int main(void){    int data[8] = { 10, 20, 30, 40, 50 };    size_t len = 5;    size_t shifts = 0;    for (size_t k = len; k > AT; k--) {        data[k] = data[k - 1];        shifts++;    }    data[AT] = 15;    len++;    for (size_t k = 0; k < len; k++)        printf("%d ", data[k]);    printf("\n%zu shifts\n", shifts);    return 0;}

main has started: data has room for 8 ints from 0x7fd0, none written yet.

shifts
0
Insert at index

Output

0.00 / 12.00 s
Describe this picture

A strip of eight int cells, int data[8], with their indices above and addresses below, from 0x7fd0, and the code of make-room.c from main on beside it, the line about to run marked. The clip takes about 12 seconds. The initialiser writes 10 20 30 40 50 into cells 0 to 4 and 0 into cells 5 to 7, which stay greyed: they are spare. Then the marker k starts at cell 5 and the loop copies backwards: 50 slides from cell 4 into cell 5, then 40 from 3 into 4, 30 from 2 into 3, and 20 from 1 into 2, each copy leaving the old value behind until the next one overwrites it. The readout “shifts” counts 1, 2, 3, 4. Cell 1 is now free; 15 drops into it, the live range grows to six cells, and the output shows 10 15 20 30 40 50 and 4 shifts. Under the strip is one sentence on the current step. After the clip, the control “Insert at index” offers 0 to 5; a press replays the clip on that program, with 5, 4, 3, 2, 1 or 0 shifts: at 5, 15 goes straight into the first spare cell. The transport plays and pauses, steps one line back or forward, and scrubs; the index and a paused step are kept in the page address as array-insert.at and array-insert.step.

Length and capacity

An array in C is a fixed run of cells. To use it as a list that grows and shrinks, keep two numbers beside it: the length, how many cells are in use, and the capacity, how many there are. The cells from the length up to the capacity are spare: they exist, and may even hold old values, but they are not part of the list.

Inserting in the middle means making a gap, and the only way to make a gap is to move every cell after it one place along. That is why the cost depends on where.

The code and its cost

Making room

int data[8] = { 10, 20, 30, 40, 50 };
size_t len = 5;
size_t shifts = 0;
for (size_t k = len; k > AT; k--) {
    data[k] = data[k - 1];
    shifts++;
}
data[AT] = 15;
len++;

AT is the index to insert at, fixed when the program is compiled (#define AT 1, or gcc -DAT=3). The loop runs k from len down to AT + 1 and copies cell k − 1 into cell k: cells 4, 3, 2 and 1 each move one place right, starting from the end. Then cell AT is free to overwrite. Inserting at i into len items takes len − i shifts: 5 at the front, 0 at the end.

The direction matters. Copying from the front would write cell 1’s value into cell 2 before cell 2’s own value had been moved, and so on down the line: one value copied over everything (the worked example shows it).

A struct that keeps them together

struct int_array {
    int *data;
    size_t len, cap;
};

The functions take a pointer to the struct, so they can change len. Each refuses when it cannot work, and returns false.

bool insert_at(struct int_array *a, size_t i, int v)
{
    if (a->len == a->cap || i > a->len)
        return false;
    for (size_t k = a->len; k > i; k--)
        a->data[k] = a->data[k - 1];
    a->data[i] = v;
    a->len++;
    return true;
}

bool delete_at(struct int_array *a, size_t i)
{
    if (i >= a->len)
        return false;
    for (size_t k = i; k + 1 < a->len; k++)
        a->data[k] = a->data[k + 1];
    a->len--;
    return true;
}

insert_at is the loop above. delete_at closes the gap the other way: cells i + 1 to len − 1 each move one place left, starting from the front, len − i − 1 moves. Then len drops by one. Nothing is given back: the block keeps all its cells, and the last one still holds its old value, now outside the length.

The program int-array.c also has append, which writes cell len if len < cap; reverse, which swaps the two ends and walks inwards; max, which compares each item with the best so far (it needs len ≥ 1, since it starts from cell 0); and print. Its main makes a block of 8 and calls them in turn.

An array that knows its length

A block of 8 ints, its len and its cap, through six operations

bool insert_at(struct int_array *a, size_t i, int v){    if (a->len == a->cap || i > a->len)        return false;    for (size_t k = a->len; k > i; k--)        a->data[k] = a->data[k - 1];    a->data[i] = v;    a->len++;    return true;} bool delete_at(struct int_array *a, size_t i){    if (i >= a->len)        return false;    for (size_t k = i; k + 1 < a->len; k++)        a->data[k] = a->data[k + 1];    a->len--;    return true;}int main(void){    struct int_array a = { NULL, 0, 8 };    a.data = malloc(a.cap * sizeof *a.data);    if (a.data == NULL)        return 1;    for (int v = 1; v <= 5; v++)        append(&a, v * 10);    insert_at(&a, 1, 15);    delete_at(&a, 3);    reverse(&a);    print(&a);    int m = max(&a);    printf("max %d\n", m);    free(a.data);    return 0;}

main has started.

call
–
len
?
cap
?

Output

0.00 / 27.80 s
Describe this picture

A strip of the eight int cells of the heap block that a.data points at, from 0x1000, every cell ”?” until written, with the code of insert_at, delete_at and main beside it; while another function runs, its call in main is marked. Readouts give the call running, “len” and “cap”. The replay takes about 28 seconds, one beat per write. Five appends fill cells 0 to 4 with 10 20 30 40 50, len rising to 5, cap staying 8; the cells beyond len stay greyed. insert_at(1, 15) moves 50, 40, 30 and 20 one place right, the marker k on each cell written, then writes 15 into cell 1: len 6. delete_at(3) moves 40 and 50 one place left, k on cells 3 and 4, and len drops to 5; cell 5 still shows 50, greyed. reverse swaps cells 0 and 4, then 1 and 3, with markers lo and hi below, giving 50 40 20 15 10. print writes that line in the output, max returns 50 and the output adds max 50. Last, free gives the block back and the whole strip greys. The transport plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as array-ops.step.

After each call the items in use are 10 20 30 40 50; then 10 15 20 30 40 50, after 4 shifts; then 10 15 20 40 50, after 2; then 50 40 20 15 10, after 2 swaps. The program prints 50 40 20 15 10 and max 50.

What each operation costs

OperationCells moved or compared
read or write cell i1 step
append (len < cap)1 write
insert at ilen − i shifts
delete at ilen − i − 1 shifts
reverse⌊len/2⌋ swaps
maxlen − 1 comparisons

Inserting or deleting near the front costs the most; at the end it costs nothing beyond the one write. On average, over every position, about half the items move.

Worked example

Delete at 3. In 10 15 20 30 40 50, deleting the 30 at index 3 moves 40 and 50 one place left: 6 − 3 − 1 = 2 moves, leaving 10 15 20 40 50.

When order does not matter. If the list need not stay in order, delete by moving the last item into the hole and dropping len by one: 1 move, wherever the hole is. Deleting the 30 that way gives 10 15 20 50 40.

At a million. In 10⁶ items, inserting at the front moves all 10⁶. At a random place, the average is about half, 5 × 10⁵ moves.

Shifting from the front, as a mistake.

for (size_t k = i + 1; k <= len; k++)
    data[k] = data[k - 1];

With i = 1 on 10 20 30 40 50, the first pass copies 20 into cell 2, overwriting the 30 before it has moved. The next pass copies that 20 into cell 3, and so on: the cells become 10 20 20 20 20 20. Writing 15 at index 1 then gives 10 15 20 20 20 20. The traced program front-first.c prints exactly those two lines.

Two checks. How many shifts does inserting at index 0 into 7 items take? (7.) How many moves does deleting the last item take? (0.)

Where it’s used

Editors and spreadsheets. A text editor’s table of lines and a spreadsheet’s rows are ordered lists; inserting a row in the middle moves those after it, which is why large files use cleverer structures.

Sorted arrays. An array kept in order for binary search pays this shift on every insertion: finding the place is quick, making room is not.

Fixed buffers. Embedded programs that cannot allocate keep a fixed array with a length beside it, exactly as here.

Reference card

Code or ideaMeaning
lencells in use
capcells allocated
appendwrite cell len, then len + 1
insert at ishift len − 1 down to i right, from the end: len − i moves
delete at ishift i + 1 up to len − 1 left: len − i − 1 moves
delete, order freelast item into the hole: 1 move
reverse⌊len/2⌋ swaps
maxlen − 1 comparisons, len ≥ 1

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look