LearnFrenzy
Sign in Take a free test

Dynamic Memory Allocation · Lesson 26 of 35

realloc and Growable Arrays in C

5 min read Updated Nov 7, 2026 Saurabh Samir

What if you don't even know how many items are coming – reading numbers until the user stops, loading lines from a file? realloc resizes a heap block, keeping its contents. With it you can build a growable array, the C equivalent of Python's list or Java's ArrayList.

In this lesson

  • How realloc works
  • Using realloc safely
  • A growable array that doubles its capacity
  • Allocating a 2D array dynamically

realloc

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *a = malloc(3 * sizeof *a);
    if (a == NULL) return 1;
    for (int i = 0; i < 3; i++) a[i] = (i + 1) * 10;

    int *bigger = realloc(a, 6 * sizeof *a);   // ask for room for 6 ints
    if (bigger == NULL) {                     // on failure the old block is still valid
        free(a);
        return 1;
    }
    a = bigger;                               // only now replace the old pointer
    for (int i = 3; i < 6; i++) a[i] = (i + 1) * 10;

    for (int i = 0; i < 6; i++) printf("%d ", a[i]);
    printf("\n");
    free(a);
    return 0;
}

Output

10 20 30 40 50 60 

What realloc(p, new_size) does

Is there free space right after the block?

  • Yesextends the block in place and returns the same address
  • Noallocates a new block, copies the old contents, frees the old block, returns the new address
  • Not enough memoryreturns NULL and leaves the old block untouched

Classic mistake: p = realloc(p, size);

If realloc fails it returns NULL – and you've just overwritten your only pointer to the old block, leaking it. Always assign to a temporary first, as above. Also remember the block may move: any other pointers into the old block become invalid.

A growable array

Growing by one element at a time would copy the whole array on every push. Doubling the capacity when it's full makes adding an element cheap on average:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct {
    int *data;
    size_t size;        // elements in use
    size_t capacity;    // elements allocated
} IntList;

bool list_push(IntList *list, int value) {
    if (list->size == list->capacity) {
        size_t new_cap = list->capacity ? list->capacity * 2 : 4;
        int *p = realloc(list->data, new_cap * sizeof *p);
        if (p == NULL) return false;
        list->data = p;
        list->capacity = new_cap;
        printf("  (grew to capacity %zu)\n", new_cap);
    }
    list->data[list->size++] = value;
    return true;
}

void list_free(IntList *list) {
    free(list->data);
    list->data = NULL;
    list->size = list->capacity = 0;
}

int main(void) {
    IntList list = {NULL, 0, 0};
    for (int i = 1; i <= 10; i++) {
        if (!list_push(&list, i * i)) { list_free(&list); return 1; }
    }
    printf("%zu squares:", list.size);
    for (size_t i = 0; i < list.size; i++) printf(" %d", list.data[i]);
    printf("\n");
    list_free(&list);
    return 0;
}

Output

  (grew to capacity 4)
  (grew to capacity 8)
  (grew to capacity 16)
10 squares: 1 4 9 16 25 36 49 64 81 100

The struct bundles the pointer with its size and capacity (structures are covered in chapter 10; list->size means "the size member of the struct list points to"). Ten pushes caused only three reallocations.

A dynamic 2D array

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;
    int *grid = malloc((size_t)rows * cols * sizeof *grid);   // one block, row-major
    if (grid == NULL) return 1;

    for (int r = 0; r < rows; r++)
        for (int c = 0; c < cols; c++)
            grid[r * cols + c] = (r + 1) * (c + 1);           // element (r, c)

    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) printf("%4d", grid[r * cols + c]);
        printf("\n");
    }
    free(grid);
    return 0;
}

Output

   1   2   3   4
   2   4   6   8
   3   6   9  12

A single block with r * cols + c indexing is simple, fast and needs one free. The alternative – an array of row pointers, each malloc'd separately – allows rows of different lengths but needs a free per row plus one for the pointer array.

Practice

Try it yourself

  1. Read numbers until 0 is entered, storing them in a growing array, then print how many were read and their maximum.

    Show solution
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        size_t n = 0, cap = 2;
        int *a = malloc(cap * sizeof *a);
        if (a == NULL) return 1;
        int x;
        printf("Numbers (0 to stop): ");
        while (scanf("%d", &x) == 1 && x != 0) {
            if (n == cap) {
                int *p = realloc(a, cap * 2 * sizeof *a);
                if (p == NULL) { free(a); return 1; }
                a = p;
                cap *= 2;
            }
            a[n++] = x;
        }
        int max = n ? a[0] : 0;
        for (size_t i = 1; i < n; i++) if (a[i] > max) max = a[i];
        printf("read %zu numbers, max %d\n", n, max);
        free(a);
        return 0;
    }

    Output

    Numbers (0 to stop): 5 12 7 30 9 0
    read 5 numbers, max 30

Quick check

Does realloc keep the old contents?

Yes, up to the smaller of the old and new sizes.

Why not write p = realloc(p, n)?

If it fails it returns NULL and you lose (leak) the original block.

Why double the capacity instead of adding 1?

Fewer reallocations and copies – pushes become cheap on average.

Summary

  • realloc resizes a heap block, possibly moving it; assign the result to a temporary first.
  • Growable arrays track size and capacity and double the capacity when full.
  • A dynamic 2D array can be one block indexed with r * cols + c.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links