Data Structures Are Not Interview Tricks; They Are the Skeleton of a System
· 42 min read · Views --

Data Structures Are Not Interview Tricks; They Are the Skeleton of a System

Author: Alex Xiang


The Programmer Craft in the AI Era column is not trying to answer whether AI will replace programmers. It asks a more practical question: after AI can already write large amounts of code, what hard things should programmers still hold onto? The first article started from “programs = algorithms + data structures”. This article keeps digging: what do data structures actually look like inside real systems?

When many people hear “data structures”, they think of reversing linked lists, traversing binary trees, and heap sort. Those things are useful, but engineering problems more often ask something different: can you see that this business problem actually needs a queue, an index, a state machine, an event log, or a tree? AI can write code from your description, but you still need to know how the system skeleton should be built.

A Data Structure Is an Access Pattern, Not a Code Shape

The essence of a data structure is not how a class is written, nor how pointers are connected in memory. For engineering systems, it is first an access pattern.

If you need to find an object quickly by ID, you usually think of a hash table or a database primary-key index. If you need to process tasks in chronological order, you think of a queue or a log. If you need to display comments, organizations, permission inheritance, or menus by hierarchy, you think of a tree. If you need keyword search over articles, logs, or products, you think of an inverted index. If you only want to keep recently accessed data, you think of LRU.

Once a structure is chosen, the system boundary is chosen with it.

For example, the same requirement, “save user operation records”, can have three very different skeletons:

StructureCommon implementationGood forPoor for
Current-state tableuser_state, one row for the latest stateFast reads of current valueHistory tracing, audit, replay
Event logAppend-only log, one row per operationAudit, replay, async consumptionDirect current-state queries
State table + logState table for queries, log for tracingMost business systemsMore complex write path

If you only ask AI to “implement user operation records”, it will likely give you a table:

create table user_actions (
  id bigserial primary key,
  user_id bigint not null,
  action text not null,
  created_at timestamptz not null
);

This is not wrong, but it has not answered the key questions: is this an audit log or business state? Can it be modified? Does it need replay? Does it need pagination by user? Will other services consume it? Is there a retention period? If these questions are unclear, all later code is guessing.

Hash Tables: The Fastest Path, and the Easiest One to Lose Control Of

Hash tables are probably the most common data structure in engineering. Caches, deduplication, aggregation by ID, connection pools, session tables, and rate-limit counters often have hash tables behind them.

In AI-generated code, the most common hash-table problem is not that AI does not know how to use one. It uses them too casually.

For example, a batch query API needs to deduplicate product IDs before querying the database. AI can easily write this:

def dedupe_product_ids(product_ids: list[str]) -> list[str]:
    return list(set(product_ids))

This code removes duplicates, but it destroys order. If the frontend sends [B, A, B, C] and expects results ordered by first occurrence, this implementation fails. A more stable version is:

def dedupe_keep_order(product_ids: list[str]) -> list[str]:
    seen = set()
    result = []
    for product_id in product_ids:
        if product_id in seen:
            continue
        seen.add(product_id)
        result.append(product_id)
    return result

This still uses a hash table, but it adds an ordering constraint. Many production bugs hide in this kind of “small difference”.

Here is another example that more easily goes wrong: local caching.

cache = {}


def get_user_profile(user_id):
    if user_id not in cache:
        cache[user_id] = load_user_profile(user_id)
    return cache[user_id]

This code looks good in a demo and dangerous in production. It has no capacity limit, no expiration time, no concurrency protection, and no distinction between cache penetration and cache breakdown. If there are enough user IDs, this dict grows forever.

When asking AI to write a cache, the prompt should at least say:

Local cache requirements:
- Use LRU and keep at most 10,000 users.
- Each entry has a TTL of 5 minutes.
- The cache key must include user_id and profile_version.
- Concurrent loads for the same key must use singleflight to avoid hammering the database.
- Emit metrics for cache hit rate, eviction count, and load failures.

At this level, a hash table is no longer just a dict. It becomes a component with capacity, time, concurrency, and observability boundaries.

Queues Are Not Just “Run It Later”

Queues usually serve three purposes in systems: absorbing peaks, decoupling, and serialization.

Peak absorption is straightforward. During request spikes, tasks enter a queue and workers consume them gradually. Decoupling is also common: after a user places an order, SMS, coupons, and analytics can run asynchronously. Serialization is easier to overlook: money movements for the same account must be processed in order.

Different purposes require different queue properties.

PurposeQueue requirementCommon pitfall
Peak absorptionCan accumulate, scale workers, and apply rate limitsOnly accumulating, with no idea when to reject new tasks
DecouplingRetryable, dead-letter capable, traceableInfinite retry after failure, or silent loss
SerializationPreserve order for the same keyMultiple workers break ordering
Delayed executionSupports run_at or delayed queuesScanning the full table to find due tasks
Priority schedulingPriority queue or multiple queuesHigh-priority tasks starve low-priority tasks

AI can easily turn a queue into “one table plus one while loop”:

while True:
    job = db.fetch_one("select * from jobs where status = 'pending' limit 1")
    if job:
        run(job)

This code is almost impossible to run safely in production. Multiple workers may claim the same task. There is no lock, no lease, no retry, no timeout, no batching, no idle wait, and no index.

A slightly more realistic database-backed queue should at least consider:

select *
from jobs
where status = 'pending'
  and run_at <= now()
order by priority desc, run_at asc
limit 100
for update skip locked;

for update skip locked matters. It lets multiple workers claim tasks concurrently without claiming the same batch. The corresponding index must also exist:

create index idx_jobs_ready
on jobs (status, run_at, priority desc);

This is still simplified. Real systems also have locked_by, locked_at, attempt, max_attempts, last_error, idempotency_key, and timeout_at. A queue is never simply “run it later”. It is the structure that controls throughput, ordering, failure, and recovery inside a system.

Trees: Hierarchical Data Fears “Just Recurse” Most

Tree structures are common in business systems: comments, menus, organizations, categories, permissions, folders, and knowledge-base nodes.

When AI writes tree-handling code, the most common problem is recursing all the way down:

def load_children(node_id):
    children = db.query("select * from nodes where parent_id = ?", node_id)
    return [
        {**child, "children": load_children(child["id"])}
        for child in children
    ]

This code is intuitive and dangerous. Each node triggers one query, so more nodes means N+1 queries. A deeper tree also risks stack overflow. If the data contains a cycle, recursion can run away entirely.

Tree design should first ask:

  • What is the maximum tree depth?
  • Does a query fetch direct children or a whole subtree?
  • Can nodes be moved?
  • Is path-based ordering needed?
  • Do we need ancestor chains?
  • Is soft delete allowed?
  • Can data contain cycles?

Different answers imply different structures.

ScenarioRecommended structureReason
Menus, shallow commentsAdjacency list with parent_idSimple writes, one or two levels are enough
Folders, knowledge-base directoriesPath enumeration with pathEasy subtree query and ordering
Permission inheritance, organizationsClosure tableStable ancestor and descendant queries
Read-only trees, large-scale displayPrecomputed tree snapshotGood read performance, cache-friendly

With path enumeration, the data may look like this:

id=1, path=/1/
id=2, path=/1/2/
id=3, path=/1/2/3/

Querying the subtree of 2 becomes:

select *
from nodes
where path like '/1/2/%'
order by path;

At this point, the path field is the data structure itself. It sacrifices node-move cost in exchange for simple and stable subtree reads. If AI does not know this tradeoff, it tends to write recursive queries because that code looks most natural.

Indexes Are Data Structures Inside the Database

Many programmers know databases need indexes, but do not necessarily think of indexes as data structures.

Indexes are absolutely data structures. B+ tree indexes are suitable for range queries and ordering. Hash indexes are suitable for equality lookup. Inverted indexes support text search. Vector indexes support approximate nearest-neighbor search. Different indexes support different access patterns.

A typical slow query looks like this:

select *
from tool_call_history
where user_id = :user_id
  and created_at >= :start
  and tool_id = :tool_id
order by created_at desc
limit 100;

If AI only sees the SQL and not the data scale, it may think the query is fine. But the index determines whether it runs in milliseconds or seconds.

This query usually needs a composite index:

create index idx_tool_call_user_tool_time
on tool_call_history (user_id, tool_id, created_at desc);

Field order is not arbitrary. user_id and tool_id are equality filters. created_at participates in both range filtering and ordering. This index lets the database locate rows by user and tool, then scan the first 100 rows in reverse time order.

If the requirement changes to:

select tool_id, count(*)
from tool_call_history
where created_at >= :start
group by tool_id;

The previous index may no longer fit. It starts with user, but this query has no user condition. You may need (created_at, tool_id), or a daily summary table instead.

When asking AI to write a data access layer, I usually ask it to output:

Please provide:
- Major query patterns
- Expected data volume for each query
- Recommended indexes
- Why the index fields are ordered this way
- Which queries cannot be optimized by this index
- Whether a summary table or materialized view is needed

This is far more useful than “write the ORM query for me”.

Logs: Append-only Is a Hard Structure

Many systems eventually grow a log: operation log, audit log, message log, event log, binlog, or WAL.

A log may look like “just write one more row”, but it is a very strong data structure. Its core constraint is append-only: append, never modify in place. That constraint brings several benefits:

  • Auditing is easier because history is not overwritten.
  • Replay is easier because order is stable.
  • Async consumption is easier because consumers can remember offsets.
  • Recovery is easier because processing can resume from a position.

Logs have costs too. They grow forever. Consumers may lag. Duplicate consumption must be idempotent. Message order may hold only within a partition. Schema evolution becomes harder.

An event-log table can be simple:

create table account_events (
  id bigserial primary key,
  account_id bigint not null,
  event_type text not null,
  event_id uuid not null,
  payload jsonb not null,
  created_at timestamptz not null default now(),
  unique(event_id)
);

Here event_id is the idempotency key. When a consumer processes the same event twice, it must recognize that it is the same event. created_at is not strict ordering; real ordering usually depends on an auto-increment ID, log offset, or version number.

If account balances are derived from accumulated events, version control is also needed:

update accounts
set balance = balance + :delta,
    version = version + 1
where id = :account_id
  and version = :expected_version;

This connects the log, state table, and optimistic lock. AI can write this SQL, but only if you tell it this is an append-only event stream rather than ordinary CRUD.

A Data-Structure Design Prompt for AI

At this point, the method can be condensed into a practical prompt.

Do not write business code yet.

Design the data structures for this feature:
1. List core entities, fields, unique constraints, and state fields.
2. List major access patterns: lookup by ID, lookup by time, hierarchical lookup,
   keyword search, status scanning, priority dequeue.
3. Choose the right data structure for each access pattern: hash table, queue, tree,
   inverted index, B+ tree index, event log, state machine, etc.
4. Provide recommended database indexes and explain field order.
5. Analyze complexity, data-volume limits, memory limits, and possible hotspots.
6. List 5 implementations AI is likely to generate incorrectly, and explain why.
7. Only then provide the code implementation plan.

This prompt is long, but it forces AI to draw the skeleton first. If the skeleton is right, code can be iterated. If the skeleton is wrong, more code only makes the system harder to save.

Back to Engineering Judgment

Data structures are not interview tricks, and they do not only live in algorithm courses. In systems, they become caches, queues, indexes, trees, logs, state machines, message bodies, and aggregate tables.

AI will make code generation cheaper and cheaper, but it will not make structure choices irrelevant. Quite the opposite: once code becomes cheap, the wrong structure can be written into the system faster.

So the skill programmers should practice is not “can I hand-write every data structure”. It is whether, when seeing a requirement, you first ask:

  • How does this feature mainly access data?
  • Can the data grow enough that memory limits or pagination are required?
  • Does it need ordering, priority, deduplication, idempotency, audit, or replay?
  • Is it read-heavy or write-heavy?
  • Are we querying current state or historical process?
  • Can indexes support the main queries?
  • Has AI-generated code hidden structural assumptions?

Once these questions are asked, AI has a chance to write code that can enter a real system. Otherwise, it merely writes your unclear thinking into technical debt at higher speed.