data-structures · intermediate · ~35 min
Stable identifiers vs. positional indices; allocation discipline.
Build a fixed-capacity to-do list with stable item IDs.
Implement the following API. Each added item gets a fresh, monotonically increasing ID; removing an item never renumbers or reuses IDs. todo_text indexes surviving items by 0-based position (in insertion order).
typedef struct todo todo_t;
todo_t *todo_create(int capacity);
int todo_add(todo_t *t, const char *text); /* returns id >= 1, or -1 if full */
int todo_remove(todo_t *t, int id); /* returns 1 if removed, 0 if not found */
int todo_count(todo_t *t);
const char *todo_text(todo_t *t, int index); /* by 0-based position */
void todo_destroy(todo_t *t);
capacity: maximum number of items (capacity > 0).text: a NUL-terminated string; the list stores its own copy.id: a previously returned item ID.index: a 0-based position among the current items.todo_add: the new item's ID (>= 1), or -1 if the list is full.todo_remove: 1 if an item with that ID was removed, 0 if not found.todo_count: the current number of items.todo_text: a pointer to the item's text at that position, or NULL if out of range.todo_create(3)
todo_add("buy milk") -> 1
todo_add("walk dog") -> 2
todo_add("write code") -> 3
todo_add("nope") -> -1 (full)
todo_remove(2) -> 1
todo_add("yet another")-> 4 (new ID, never reuses 2)
todo_text(0) -> "buy milk"
todo_text with an out-of-range index returns NULL.strdup and free it on remove and destroy.A CRUD-style to-do list with stable identifiers is the simplest realistic API design exercise. It teaches you the difference between an index (positional) and an ID (stable, never reused).
capacity > 0; text NUL-terminated; id a returned ID; index 0-based.
See the API: add returns an ID or -1; remove returns 1/0; text returns a pointer or NULL.
Use strdup for text; free on remove and destroy. IDs are never reused.
typedef struct todo todo_t;
todo_t *todo_create(int capacity);
int todo_add(todo_t *t, const char *text);
int todo_remove(todo_t *t, int id);
int todo_count(todo_t *t);
const char *todo_text(todo_t *t, int index);
void todo_destroy(todo_t *t);
Reusing IDs after delete (breaks every caller that stored the ID). Confusing index with ID. Forgetting to free the strdup'd text on remove and destroy.
Add when full returns -1. Remove non-existent returns 0. Index out of range for todo_text returns NULL.
O(n) per op (linear array). A future variant could use a balanced tree for O(log n).
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.