Topic 69 of 100 Arrays and Strings in C Interactive Tutorial

Array of Strings in C: Storing and Accessing Multiple Text Values

12 mins read Last updated: 22 Aug 2026 24,300 views
C Compiler 0.00%

What is Array of Strings in C? Core Concept & Direct Summary

Core Objective & Problem Statement

How do you represent collections of strings using 2D character arrays vs arrays of pointers in C?

Direct Answer Summary

One string is a char array. So what do you do when you need fifty? A list of names, a set of menu options, a table of words to search?

The answer, in C, is an array of strings — which sounds simple until you realize C gives you two completely different ways to build one, with genuinely different trade-offs. One is a 2D char array; the other is an array of pointers. Beginners often pick the wrong one and then fight the limitations. This tutorial covers both, side by side, so you can choose deliberately.

(Suggested image: the hero graphic — two side-by-side memory diagrams: a rigid 2D grid of fixed-width rows vs a row of pointers each reaching to a right-sized string)

How Does Array of Strings in C Work? In-Depth Mechanics & Architecture

The Two Approaches at a Glance

main.c
Run Online
// Method 1: 2D char array — a rectangular gridchar days[7][10] = {
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
}
;
// Method 2: array of pointers — each points to a stringchar *months[] = {
"January", "February", "March", "April"
}
;

Both let you write days[2] / months[1] and get the third day / second month. Both print with %s. But under the surface they're different data structures with different capabilities — and the differences determine which one you should pick.


Method 1: The 2D char Array (A Grid of Characters)

main.c
Run Online
char names[3][20] = {
    "Alice", "Bob", "Charlotte"
}
;

What this declares: 3 rows, each holding up to 20 characters. Total memory: 3 × 20 = 60 bytes, regardless of how long the names actually are.

main.c
Run Online
[0] [1] [2] [3] [4] ... [18] [19][0] A l i c e \0 ? ? ← rest is unused[1] B o b \0 ? ? ? ?[2] C h a r l o t t e \0 ...

Accessing:

main.c
Run Online
printf("%s\n", names[0]);
// Aliceprintf("%c\n", names[2][0]);
// C — first char of Charlotteprintf("%s\n", names[2] + 4);
// lotte — starting from index 4
names[1][0] = 'R';
// ✓ writable — now "Rob"strcpy(names[1], "Robert");

Determining how many strings:sizeof(names) / sizeof(names[0]) → 60 / 20 = 3 rows. (Careful: that gives you the row count, not the length of any individual string — use strlen(names[i]) for those.)

Advantages of the grid

  • Writable. Every row is your own memory, so you can modify characters freely. This is the big one.
  • Simple, single allocation. One declaration, no pointers to track.
  • Easy iteration.names[i] and names[i][j] behave exactly like the 2D arrays you already know.

Drawbacks of the grid

  • Every row costs the full width. Twenty bytes per name even if the name is three characters. Fifty names with a 50-byte row limit is 2,500 bytes, mostly wasted space.
  • The longest string sets the width for everyone. Decide the column size wrong (too small) and your strings silently truncate or overflow.
  • Wasted space adds up quickly. For large lists this matters a lot.

Method 2: The Array of Pointers (A List of Addresses)

main.c
Run Online
char *names[3] = {
    "Alice", "Bob", "Charlotte"
}
;

What this declares: an array of 3 char * pointers — 3 addresses (24 bytes on a 64-bit system), plus the strings themselves stored wherever the literals live (usually a read-only data segment).

Architecture Diagram
names[0] ──→ "Alice"names[1] ──→ "Bob"names[2] ──→ "Charlotte"

Each string gets exactly the space it needs — no padding, no fixed-width waste. And the pointers themselves form a simple array you can index, loop over, or sort.

Accessing:

main.c
Run Online
printf("%s\n", names[0]);
// Aliceprintf("%c\n", names[2][0]);
// C — same syntax!

Note that the access syntax is identical to the 2D array version. names[2] gives you a string either way; names[2][0] gives you a character either way. This is why the two are so easy to confuse — they behave alike in the common cases and diverge in the important ones.

The critical limitation

If the pointers reference string literals, the text is read-only:

main.c
Run Online
char *names[3] = {
    "Alice", "Bob", "Charlotte"
}
;
names[0][0] = 'M';
// ✗ UNDEFINED BEHAVIOR — likely a crash!

This is the literal-vs-array rule from the strings basics page, applied at array scale. You can reassign a pointer to point at different text:

main.c
Run Online
names[0] = "Malice";
// ✓ fine — you're changing the pointer, not the textnames[0] = "Alicia";
// ✓ fine

...but you can't modify the characters themselves. If you need to edit in place, either use Method 1, or allocate writable copies with strdup (POSIX) or malloc + strcpy.

(Suggested image: the pointer-array memory diagram with the literal pool marked read-only (padlock) and arrows from each pointer, contrasted against the writable 2D grid)


The Comparison: Which Should You Use?

This is the decision table worth keeping:

2D char array char a[R][C]Array of pointers char *a[N]
Memory per stringFixed width C, alwaysExactly what's needed (plus the pointer)
Memory for N stringsN × C bytesN pointers + actual string lengths
Writable characters?✓ Yes✗ Not if literals (crash risk)
Can reassign a whole string?Only by copying chars in✓ Yes — just change the pointer
Can strings vary in length?Yes, but limited by CYes, unlimited
Efficiency for large listsWastes space on short stringsCompact
Easiest to read/write✓ Simpler for beginnersSlightly more pointer-heavy
Best forModifiable text, input buffers, fixed formatsConstant lists, lookup tables, many varying-length strings

The practical guidance:

  • Reading input into strings → 2D array (you need writable memory)
  • A constant list of options, days, months, keywords → array of pointers (compact, and you never modify it)
  • A list where strings vary wildly in length → array of pointers (no wasted width)
  • A list where you'll modify characters in place → 2D array

When in doubt for input, use the 2D array — you can't crash it by writing to a literal.

(Suggested image: a small decision-flow graphic — "Do you need to modify the text? → 2D array. Just read it? → pointer array.")


Reading Multiple Strings From the User

Because input needs writable memory, this is Method 1's home turf:

main.c
Run Online
#include <stdio.h>
#include <string.h>
#define MAX_NAMES 5
#define MAX_LEN 30
int main(void) {
    char names[MAX_NAMES][MAX_LEN];
    int count;
    printf("How many names (1-%d)? ", MAX_NAMES);
    scanf("%d", &count);
    if (count < 1 || count > MAX_NAMES) {
        puts("Invalid count.");
        return 1;
    }
    // Clear the leftover newline from scanf    int c;    while ((c = getchar()) != '\n' && c != EOF);
    for (int i = 0; i < count; i++) {
        printf("Name %d: ", i + 1);
        fgets(names[i], MAX_LEN, stdin);
        names[i][strcspn(names[i], "\n")] = '\0';
        // strip newline    }
        puts("\n--- You entered ---");
        for (int i = 0; i < count; i++) {
            printf("%d. %s (%zu chars)\n", i + 1, names[i], strlen(names[i]));
        }
        return 0;
    }

Sample Run:

main.c
Run Online
How many names(1-5)? 3Name 1: Priya SharmaName 2: RaviName 3: Mei Ling Chen
--- You entered ---1. Priya Sharma(12 chars)2. Ravi(4 chars)3. Mei Ling Chen(13 chars)

Four details doing real work here:

  1. fgets(names[i], MAX_LEN, stdin) — passes names[i], which is the row (a char[30], which decays to a char *). Exactly the array-to-function behavior from the arrays cluster.
  2. names[i][strcspn(names[i], "\n")] = '\0'; — a 2D indexed use of the newline-stripping idiom.
  3. The getchar() flush loop — clearing the newline scanf left behind, or the first fgets would immediately read an empty line. You met this in our scanf and getchar tutorials.
  4. strlen(names[i]) — per-row length, because the grid's fixed width tells you nothing about the actual text length.

Note that spaces work fine here — "Priya Sharma" and "Mei Ling Chen" both came through intact, which scanf("%s") could never manage.


Sorting an Array of Strings

A classic exercise, and a good demonstration of how arrays of strings interact with strcmp and strcpy.

Sorting a 2D char array — you compare with strcmp and swap with strcpy (you can't swap arrays with =):

main.c
Run Online
#include <stdio.h>
#include <string.h>
#define N 5
#define LEN 30
int main(void) {
    char names[N][LEN] = {
        "Charlie", "Alice", "Ethan", "Bob", "Diana"
    };
    // Bubble sort — comparing with strcmp, swapping with strcpy    for (int i = 0; i < N - 1; i++) {        for (int j = 0; j < N - 1 - i; j++) {            if (strcmp(names[j], names[j + 1]) > 0) {                char temp[LEN];                        // temporary buffer for the swap                strcpy(temp, names[j]);                strcpy(names[j], names[j + 1]);                strcpy(names[j + 1], temp);            }        }    }
    puts("Sorted names:");
    for (int i = 0; i < N; i++) {
        puts(names[i]);
    }
    return 0;
}

Output:

main.c
Run Online
Sorted names:AliceBobCharlieDianaEthan

Sorting an array of pointers is much lighter — you only swap addresses, no string copying at all:

main.c
Run Online
char *names[] = {
    "Charlie", "Alice", "Ethan", "Bob", "Diana"
}
;
int n = 5;
for (int i = 0;
i < n - 1;
i++) {
    for (int j = 0;
    j < n - 1 - i;
    j++) {
        if (strcmp(names[j], names[j + 1]) > 0) {
            char *temp = names[j];
            // swap POINTERS — cheap!            names[j] = names[j + 1];
            names[j + 1] = temp;
        }
    }
}

Same result, but the swap moves two pointers instead of copying up to 30 bytes three times per exchange. This is the pointer array's killer advantage: sorting, reordering, and shuffling cost almost nothing, because you're rearranging addresses rather than data.

That's also why library sorting functions work on pointers — and it's a big reason real C code frequently uses char *list[] for collections of text.

(Suggested image: both sort approaches side by side — one showing three strcpy operations per swap, the other showing a single pointer exchange)


A Complete Example: A Command Handler

Programming languages and shells are built on arrays of strings — a table of keywords, checked against input. Here's a small version:

main.c
Run Online
#include <stdio.h>
#include <string.h>
#define MAX_CMD 64
// Fixed command vocabulary — read-only, so a pointer array is idealconst char *commands[] = {"help", "add", "list", "clear", "quit"};const int   numCommands = 5;
void showHelp(void) {
    puts("Available commands:");
    for (int i = 0; i < numCommands; i++) {
        printf("  %s\n", commands[i]);
    }
}
int findCommand(const char *input) {
    for (int i = 0; i < numCommands; i++) {
        if (strcmp(input, commands[i]) == 0) {
            return i;
            // found at index i        }    }    return -1;                              // not found}
            int main(void) {
                char input[MAX_CMD];
                puts("Type 'help' for commands.");
                showHelp();
                while (1) {
                    printf("\n> ");
                    if (fgets(input, sizeof(input), stdin) == NULL) break;
                    input[strcspn(input, "\n")] = '\0';
                    if (input[0] == '\0') continue;
                    // empty input — skip
                    int cmd = findCommand(input);
                    switch (cmd) {
                        case 0: showHelp();
                        break;
                        case 1: puts("Item added (simulated).");
                        break;
                        case 2: puts("Listing items (simulated).");
                        break;
                        case 3: puts("Screen cleared (simulated).");
                        break;
                        case 4: puts("Goodbye!");
                        return 0;
                        default: printf("Unknown command: '%s'\n", input);
                        puts("Type 'help' for a list.");
                    }
                }
                return 0;
            }

Sample Run:

main.c
Run Online
Type 'help' for commands.
Available commands:
help
add
list
clear
quit
> list
Listing items(simulated).
> frobnicate
Unknown command: 'frobnicate'
Type 'help' for a list.
> quit
Goodbye!

Look at the design choices, each following the guidance from this page:

  • const char *commands[] — a constant list of strings, never modified. Array of pointers, compact, and const (from the array-parameters tutorial) documents that nothing touches it.
  • findCommand takes a const char * — it reads the input and the table, modifies neither.
  • strcmp(...) == 0 for the match — the zero-means-equal rule from the string functions reference.
  • switch on the returned index — the multi-way dispatch pattern from the control flow cluster, now driven by string data.

And crucially: a linear scan through the command table is exactly what array-of-strings is for. Add a sixth command and you edit one array — nothing else changes. This is how real command interpreters and keyword-driven programs are structured.


Common Mistakes

  1. Assigning a string into a 2D char array row with = — names[0] = "Alice"; is a compile error (arrays aren't assignable). Use strcpy(names[0], "Alice");.
  2. Writing to a literal-backed pointer array — char *a[] = {"x"}; a[0][0] = 'y'; crashes. Use a 2D array if you need to modify.
  3. Column size too small — char names[5][10] with a 12-character input overflows or truncates. Size generously, or use an array of pointers with malloc copies.
  4. Confusing row count with string length — sizeof(names)/sizeof(names[0]) gives rows, not text lengths. Use strlen per row.
  5. Using == when comparing rows — names[0] == names[1] compares addresses. Use strcmp.
  6. Trying to swap rows with = — you must copy characters (strcpy + temp buffer) for 2D arrays, or swap pointers for pointer arrays. The pointer version is much cleaner.
  7. Reading input directly into a pointer array — char *names[5]; fgets(names[0], 30, stdin); reads into a null/invalid pointer. Input needs allocated memory: use a 2D array, or malloc each row first.
  8. Forgetting fgets's newline on each row — every string then carries a trailing \n, breaking comparisons and output formatting.
  9. Mixing the two forms in one declaration — char *names[5][30] isn't what you think. Know exactly which data structure you're building.

(Suggested image: "9 Array-of-Strings Mistakes" checklist graphic — consistent with the series)


Final Thoughts

An array of strings in C comes in two shapes, and choosing between them is a genuine design decision rather than a style preference. The 2D char array gives you writable storage in a fixed grid — ideal for reading input. The array of pointers gives you compact, cheap-to-rearrange references to text you don't intend to modify — ideal for constant tables.

The dividing question is short: will you modify the characters? If yes, you need the grid. If no, the pointer array will be smaller and faster. And if you need both — a large collection you'll edit — that's the signal to move to dynamic allocation, where each string gets its own precisely-sized malloc'd buffer (a Pointers cluster topic coming later).

Get the two forms straight and you've covered the way nearly all C programs handle collections of text — from command tables to file listings to configuration options.


Runnable C Code Example & Practical Implementation

main.c
Run Online
#include <stdio.h>
int main() {
    // Array of string pointers (Ragged Array)
    const char *languages[] = {
        "C Programming",
        "Python",
        "JavaScript",
        "Rust",
        "Go"
    };
    size_t count = sizeof(languages) / sizeof(languages[0]);
    printf("Programming Languages List:\
");
    for (size_t i = 0; i < count; i++) {
        printf("  %zu: %s\
", i + 1, languages[i]);
    }
    return 0;
}

Key Takeaways, Best Practices & Memory Safety Rules

Best Practice & Key Takeaways
Use arrays of pointers const char *list[] for static string tables to eliminate wasted memory padding.

Frequently Asked Questions & Technical Interview Insights

Technical Interview & Viva Questions

Frequently asked C programming interview questions
Q

Q: What is the memory advantage of `const char *arr[]` over `char arr[100][50]`?

A: `char arr[100][50]` statically consumes 5,000 bytes regardless of actual text lengths, whereas `const char *arr[]` consumes only pointer sizes plus actual string lengths without wasted padding.
Q

Q: How do you access the 3rd character of the 2nd string in `char names[5][20]`?

A: `names[1][2]`.

Related Tools, Tutorials & Authoritative References

Recommended Interactive Tools

Authoritative Standards & Citations