Programming

Data structures, at working level

A data structure is the shape you keep several things in together, and choosing one is really choosing which question about that data gets a fast answer later. The three shapes that cover ninety percent of everyday work are the list, the dictionary and the set, and what separates them is not what they store but what they find cheaply.

  • Lesson 8 of 14
  • Beginner
  • Free, no signup

Choosing a data structure from the question you will ask later

What will you ask of this data later?the answer to that question is your data structure
  • List

    Does swapping two members change the meaning? Then the order is itself information.

    order
  • Dictionary

    Do you reach one particular thing by one particular name? Unlike a number, a key does not move.

    key
  • Set

    Do you only need to know whether it is there? It also drops duplicates at the moment they go in.

    membership
  • Tuple

    The same as a list, with the guarantee that it cannot change after it is built.

    fixed

All four of these shapes can hold the same data. What separates them is what they find cheaply, not what they store.

Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.

What is a data structure, and why does the choice matter?

A data structure is the shape you keep several things in together. So far that sounds simple, and its simplicity is exactly why its importance goes unnoticed: most beginners assume that choosing between these shapes is a matter of taste, because any of them can hold the same data.

Any of them really can. A hundred names can go in a list, in a dictionary, or in a set. The difference is somewhere else: each shape answers one question cheaply and every other question expensively.

So here is the rule to take away from this lesson. When deciding what shape to keep data in, do not look at the data; look at the question you are going to ask of it later. "Which is the third member" is one question, "what is the value of this key" is another, and "is this thing in here at all" is a third. Three questions, three shapes.

And so that you expect the right thing: the wrong choice usually does not break the program. The program works, your code simply has to do a lot of work to ask a simple question, and that extra work shows up when the data grows or when something is added to it. The last section of this lesson is a real example of exactly that.

The list: when order is itself information

A list is several things one after another, with their order preserved. That preserved order is the only thing separating a list from the rest, and it matters more than it looks.

prices = [12000, 8000, 15000]

prices.append(9000)
print(prices[0])
print(len(prices))

Every member has a number called an index, and the numbers start at zero rather than one. So prices[0] is the first price. Starting at zero has a historical reason and is the same in nearly every modern language, and it is also why it is the source of the off-by-one error you met in the conditions and loops lesson.

A list is right when the order carries meaning: the steps of a task, search results in rank order, the messages of a conversation. In all of those, swapping two members changes the meaning, and that is the signal that a list is the correct choice.

And one property that looks like an advantage at first and becomes a cost later: a member's index is not fixed. Remove the first member and every later member moves up a place and takes a new index. Which is to say "member number three" tells you nothing about that member; it only says where it stands in the list right now. If you have stored that number somewhere to use later, you have just built a bug that has not happened yet.

The dictionary: when a key replaces a number

A dictionary is pairs of a key and a value. Instead of finding something by its number, you find it by its name. Different languages give it different names, but the idea is the same.

user = {"name": "sara", "city": "shiraz"}

print(user["city"])
user["city"] = "tabriz"

Here is the important difference that usually gets skipped: when you write user["city"], the program does not search through the dictionary. It goes straight to the right place. Had you kept the same data in a list of pairs, finding the city would mean reading the whole list from the start until you reached the pair whose key is the city.

On three members that difference is invisible. On three thousand members, and especially when that search sits inside a loop, it is exactly what makes a page slow. Most of the slow code we have seen on client sites is of precisely this kind: a linear search inside a loop, where one key would have been enough.

A second property that matters just as much: a key does not change. Unlike a list index, which shifts as things are added and removed, the key stays the key. If you need to come back to one particular member later, this is the only property you actually need, and the last section of this lesson shows how seriously that plays out in practice.

Number or key? The difference shows when the data changes

List: the address is a number

the numbers start at zero

  • The number says where it stands right now
  • Remove a member and every later number changes
both hold the same data

Dictionary: the address is a key

you choose the key yourself

  • The key says which member it is, not where
  • Whatever is added or removed, the key stays the key

and here is where the difference shows

Finding one particular member

the job usually done inside a loop

  • In a list: read from the start until you find it
  • In a dictionary: go straight to the key

On data that never changes these two are equivalent and the choice really is a matter of taste. The difference starts the moment something is removed or added.

The set: when all you want to know is whether it is there

A set is several unique things with no defined order. It does two jobs very well and every other job not at all: it removes duplicates, and it answers quickly whether something is inside it.

tags = {"seo", "speed", "seo"}

print(len(tags))
print("seo" in tags)

The third line prints two, not three, because the duplicate was dropped at the moment it went in. That behaviour is what makes a set useful and it is also what silently shrinks your data if you are not paying attention; if the repetitions mean something to you, a set is the wrong shape.

Now a real world example, from this site's own code. PHP has no data type called a set at all. So when we need one, we build it out of an array whose keys are the things we want to track and whose values do not matter in the slightest:

$used = array();

// ... inside a loop that builds an id for each heading:
while ( isset( $used[ $id ] ) ) {
    $id = $base . '-' . $n++;
}
$used[ $id ] = true;

Look at that true: it is never read anywhere in the program. What is really being stored is the key itself, meaning "this id has already been used". That is precisely the definition of a set, built out of the tool that happened to be available. And the reason a list is no good here is the reason from the previous section: isset goes straight to the key, while hunting for an id in a list of ids means reading the whole list, inside a loop that runs once per heading.

Which one goes where?

The three shapes above, plus one whose name has not come up yet, the tuple, cover almost everything you need in a first year. A tuple is a list with one difference: it cannot be changed after it is built. It suits things that inherently should not change, such as a pair of latitude and longitude.

ShapeOrdered?Duplicates?The question it answers cheaply
ListYesYesWhich is member number three?
DictionaryInsertion orderNo duplicate keysWhat is the value of this key?
SetNoNoIs this member in here?
TupleYesYesSame as a list, but guaranteed not to change

The last column is the most important column of this table and the rest only explain it. If you are unsure which to choose, write the sentence you are going to ask of this data later and see which row it lands in.

And a position that does not sit well with the usual tutorials: in everyday web work the dictionary comes up more often than the list and is used less often than it should be. Beginners pour everything into lists because the list is the first thing they learned, and then loop through them to find one member. Change that single habit and a good share of the slow code you would write later never gets written at all.

A real decision: why the release ledger for these lessons is not a list

The very learning section you are reading publishes its lessons one at a time on a schedule: three a day, taking turns across sixteen tracks. The order of those turns is not stored; it is recomputed from the lessons themselves each time.

And right there is a real problem. When a track that had been empty gets filled, its new lessons land in the middle of the queue and the position of every lesson after them moves up by one. Now suppose the ledger recording what has been published were a list of positions, or even just a number saying the first five are out. A lesson published yesterday would, after today's reshuffle, no longer be among the first five; it would fall off the list and its page would 404 for a visitor.

So the ledger is a dictionary. Its key is the lesson's own address and its value is the moment it first went out. On the day this lesson was checked it held five keys, the first of which was this:

'seo/how-search-engines-work' => 1788539040

Now, however much the queue changes, this key stays where it is, because the key was never a position in the queue; it was the lesson's own name. The whole story in one sentence: a list answers what is in position three, and a dictionary answers when this particular lesson went out. Only the second question still means anything once the queue is rebuilt.

This is what the first section of this lesson said, only this time on a real page: the wrong choice would not have broken the program. The program would have worked, and one morning a few published pages would simply have vanished without a sound.

When the queue is rebuilt, what moves and what does not

  1. 1

    A new track gets filled

    A few new lessons are added to the section.

  2. 2

    The queue is recomputed

    The turns are laid out round robin across the tracks.

  3. 3

    The new lessons land mid queue

    The position of every lesson after them moves up by one.

  4. 4

    With a list: yesterday's lesson vanishes

    No longer among the first five, it would drop off the list and its page would 404.

  5. 5

    With a key: nothing moves

    The key was never a position; it was the lesson's own name.

This path is real for this learning section, but its final failure never happened; the ledger was keyed from the start. It is drawn here to show what the wrong choice would have looked like.

The fast path, with AI

What genuinely got faster in this topic is not writing a list or a dictionary, because that was never slow. What got faster is spotting that data already sitting in your program is being kept in the wrong shape. That used to take years of experience and now it takes one message, provided you ask the right question: do not ask "make this code better", ask "which questions is this data used to answer". It is judgment work, so it wants a frontier model; our current pick among coding models sits in the AI section of this site.

  1. Copy the place where the data is built together with every place it is read. A data structure cannot be judged from where it is created; what decides is the question asked of it later, and that question is written somewhere else.
  2. Send the recipe below and read only the table of questions first, before any suggestion. If that table contains a question you did not think your program asked, stop there; you have probably just learned what your code actually does.
  3. Accept a proposal to change the shape only when you have seen its matching question in the table. Reshaping data that nobody asks an expensive question of is pure risk with no gain.
  4. After the change, look for every place that had stored a number or a position somewhere. Those are the places that break quietly, because once the shape changes the number you saved no longer points at the same member.

Copy-ready recipe

I want to know whether the data below is kept in the right shape. Do not change the code.

Where the data is built:
{paste the code here}

Where it is read:
{paste every place this data is used}

First, give only a table with these three columns and no other commentary:
| Question the code asks of this data | Where it is asked | What is done to answer it |

After the table:
1. Say which of these questions is answered expensively, meaning the whole collection is read to answer it.
2. If the shape should change, say to what and which row of the table it makes cheap.
3. If the current shape is right, say so and write down why. "Everything is fine" is an acceptable answer.
4. List every place that relies on the number or position of members and would break if the shape changed.

If any of this needs code I did not give you, say which; do not guess.

Before you trust the output: The third clause is there deliberately, and removing it means the answer will always be a proposal to change something, because you asked the model for a proposal and it gives you one. Most of the data in a real program is in the right shape and changing it is pure risk. The fourth clause is not there by accident either: the one thing that quietly breaks a reshaping is a place that stored a member's number and, after the change, that number points at a different member. Do not apply the change until you have seen those places yourself.

AI in this kind of work

Building a list or a dictionary is not something a language model gets wrong; these structures repeat so often in training data that producing them is no longer a skill. What is left for you sits elsewhere: recognising whether the data you already have is kept in the right shape, and that recognition comes not from the data but from the questions the code asks of it. Our position is to use the model for counting those questions rather than for proposing a structure: counting is the job it does well and you have no patience for; choosing is the job it does having seen only one part of the program, while you know the whole of it.

Tools that actually help

  • Claude Code The best fit for this topic, because the question "where is this data read" only has an answer when the whole repository is visible and one snippet is not enough. It installs free but does not run without a Claude subscription or an Anthropic Console account, and Iran is not on the supported-countries list.
  • Claude A good choice when you paste the code yourself, and it does the job of building that table of questions from the fast path. Iran is on neither of Anthropic's two supported-countries lists; we read that on Anthropic's own page rather than measuring it.
  • Gemini Its study mode, Guided Learning, asks questions instead of handing over answers, which works better for settling the difference between an index and a key, the core of this lesson, than a ready made answer does. Google's own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.

Where it backfires

We have met the specific risk in this topic often enough in practice: ask "make this code better" and the model will nearly always propose a structural change, because you asked for a proposal and "everything is fine" does not look to it like a useful answer. But most of the data in a real program is in the right shape and changing it only adds risk. The way to close that off is written into the fast path recipe: say plainly that "no change is needed" is an acceptable answer.
The second risk is more precise. When a model proposes turning a list into a dictionary, it usually sees only where the data is built, not every place it is read. If some code stored a member's number somewhere, that code quietly points at the wrong member after the change and no error is produced at all. Anthropic itself calls this kind of confidence about what the model has not seen hallucination in its own documentation and explains how to reduce it. For how each of these tools can be paid for from Iran, see the buying guide, and for our current pick among coding models, the best AI for coding.

Sources: Anthropic: reduce hallucinations Anthropic: supported countries Claude Code: overview and install Google: where the Gemini web app is available

Where this advice stops

This lesson is deliberately written at working level and we state its boundary plainly. First, nowhere on this page have we gone into computational complexity and big O notation. The words "cheap" and "expensive" you read are descriptions rather than measurements, and we give no number about speed because we have not measured it on this server. Second, the structures that come next in any data structures book are absent here: stacks, queues, trees, graphs and priority queues. Each makes one specific question cheap, and the day you run into that question you will know what you are looking for. Third, the names and the guarantees differ between languages: PHP has a single array type that is both a list and a dictionary, while JavaScript has objects and Maps as two separate things with different behaviour. And finally, this lesson is about data that fits in one program's memory; once the data outgrows memory the conversation moves from data structures to databases, which is another lesson's subject.

From our own work

Both examples in this lesson come from this site's own code and were read again on the day this page was checked. The first is the set built out of an array: the expression $used[ $id ] = true; appears in three theme files, inc/editorial.php, inc/helpers.php and inc/diagram-posts.php, and in all three it sits directly under a while ( isset( $used[ $id ] ) ). The true is read in none of those three places; what is stored is the key itself. The second is the release ledger of this very learning section: a database option named rgbl_announced, which on the day this lesson was checked held five keys and has exactly this shape, a lesson address mapped to a timestamp. What the two examples show together, and what no introductory book writes down, is that in practice you often do not choose a data structure at all, you build one out of whatever your language gives you; what you really choose is what the address of each member will be, and that choice is what decides what breaks when the data changes.

Real follow-up questions

What is the difference between an array and a list?

In everyday talk they are the same thing and the name depends on the language: Python calls it a list, JavaScript and PHP call it an array. To be precise, in some languages an array has a fixed size while a list can grow and shrink. You do not need to worry about that distinction when starting out; what matters is that the order is preserved and each member's address is a number.

When should I move data from a list to a dictionary?

When you notice you are looping to find one particular member. That loop is exactly the place where a key would do the job. But if you only walk the data from start to finish and never ask for one specific member, the list is what you need and changing it is pure risk.

Do I need to study data structures and algorithms seriously to start?

Not to start. These three shapes, plus knowing which question each answers cheaply, cover the first year of practical work. Serious study becomes necessary in two places: when your data grows and you meet real slowness, and when you are preparing for a job interview, which unfortunately has little to do with everyday work.