Programming

Variables and data types

A variable is a name you give a value so that you can call it up later, and the type of that value decides what can be done with it: numbers add, strings join, and adding one to the other raises an error. The hardest data type bugs are not the ones that raise an error; they are the ones that pass quietly and print a wrong number.

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

The five types that circle every value

The type decides what can be done with a value, not how important the value is.

Number, whole and decimalString, any text
A valueevery value has a typeBoolean, true or false
Dictionary, by keyList, in order

These five are not all the types. Python has others and in bigger programs you build your own; but the first months pass almost entirely with these.

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

What exactly is a variable, and how do you make one?

A variable is a name you give a value. That is all. In Python making one is a single line and needs no extra keyword:

city = "Tehran"
visits = 1200

Now wherever you write city, Python puts that string there. The equals sign has no mathematical meaning here either; it does not say these two are equal, it says attach this name to that value. Which is why this line is perfectly sensible in code and meaningless in mathematics:

visits = visits + 1

The usual "a variable is a box" comparison helps up to a point and then misleads you. More precisely: a variable is a label stuck onto a value. Two labels can sit on one value, and that is what builds a real trap in section five of this lesson.

Three naming rules that make life easier from day one. Start the name with a letter and put no space in it; use an underscore instead, as in total_price. Do not shorten a name to type less, because three weeks later tp is a riddle even to you. And write names in English even when the program is Persian, because the rest of the code you will read is written that way.

The five types that do the everyday work

The figure at the top of this page arranges five types around one centre, and that centre is the value itself. Other languages have more types, but for your first months these five do nearly all the work.

price = 120000
rate = 1.09
name = "Sara"
is_paid = True
items = ["pen", "book"]
user = {"name": "Sara", "city": "Tehran"}

Whole numbers and decimals are two separate types, and that separation earns its keep later. A string is text and always sits in quotes. A boolean has only two values, True and False, and it is the heart of every condition. A list holds several values in order and you reach them by number. And a dictionary keeps values by key rather than by number, so instead of "the third one" you say "city".

print(items[0])
print(user["city"])

Counting starts at zero, so items[0] is the first member. That is one of the things that annoys you a few times in the first month and then becomes ordinary.

If you do not know what you are holding, ask Python itself. The type function answers, and it is our main tool throughout this lesson:

print(type(price))
print(type(name))

Why does age plus one raise an error?

This is everybody first real meeting with data types, and it almost always starts with input. Run this:

age = "17"
print(type(age))
print(age + 1)

The second line prints <class 'str'> and the third knocks the program over. Here is the exact message, the one we got when we ran it:

TypeError: can only concatenate str (not "int") to str

Translated: the plus sign between two strings means joining and between two numbers means adding, but between a string and a number it means nothing, and Python does not guess which one you wanted. The fix is one word:

age = "17"
print(int(age) + 1)

The output is 18. int turns a string into a whole number, float into a decimal, and str goes the other way. Three functions, and ninety per cent of conversion work is done with them.

One point that pays off later in conditions: in Python a string and a number are not equal even when they look alike. The expression "7" == 7 evaluates to False. If you ever have a condition you were sure should be true and it was not, check this first.

And a warning about conversion: int("abc") raises an error, and that is correct behaviour. If you find somebody who silenced that error with an empty try, the program no longer crashes but from then on it quietly produces wrong data, which is worse.

From the text a user typed to a number you can calculate with

The third step is the one you write; leave it out and the program falls over at the fourth.

  1. 1

    The input arrives

    Always a string, even when the user typed a number.

  2. 2

    Decide: what is this meant to be?

    A postcode stays a string, an age becomes a number. Only you know which.

  3. 3

    Convert with int or float

    One word, and it is the word most beginners leave out.

  4. 4

    Now you can calculate

    From here on the plus sign means adding, not joining.

If the string is not a number at all, the conversion raises an error at the third step, and that is correct. Silencing it means sending wrong data on to the fourth.

Why does adding two decimals not give what you expect?

Run this and look at the output:

print(0.1 + 0.2)

It prints 0.30000000000000004. This is not a Python bug; JavaScript, PHP and nearly every other language do the same. The reason is that a computer keeps decimals in base two, and some fractions do not terminate in base two, exactly as one third does not terminate in base ten. Python own documentation gives a whole page to it.

For display the fix is simple:

print(round(0.1 + 0.2, 2))

which prints 0.3. But that only fixes the display, and it is not enough for money.

Learn the money rule here, because the most expensive bugs a new developer writes are of this family: do not keep money in a decimal. You have two correct routes. One is to keep everything in the smallest unit as a whole number, so integer rials or tomans rather than fractional ones. The other is the decimal module, which exists for exactly this.

We took the first route ourselves and it can be seen. The prices on this site are converted from dollars to tomans, and in that same line the result is rounded to the nearest thousand tomans and then cast to a whole number. That is why no price on this site has a decimal part; not because it looks tidier, but because a fraction of a toman does not exist in the world, and any number that carries one shows itself in a sum sooner or later.

List or dictionary: which one goes where?

The rule in one sentence: if the things are of one kind and their order matters, a list; if each one has a name and you call it by that name, a dictionary. The figure below puts that in two columns, and neither column is better than the other.

But there is a trap in lists that surprises people more than anything else in this lesson. Remember that a variable is a label and not a box? This is what follows from it:

a = [1, 2]
b = a
b.append(3)
print(a)

The output is [1, 2, 3], not [1, 2]. We added something to b and a changed too, because both names are stuck onto one list and no second list was ever made. If you really want a separate copy, you have to say so:

c = a.copy()
c.append(4)
print(a, c)

which prints [1, 2, 3] [1, 2, 3, 4]. Now they are two separate things.

Why does this matter? Because in larger programs you pass a list to a function, the function changes it, and you believe your own copy is untouched. It raises no error and nothing falls over; your data just quietly changes. Strings and numbers do not behave this way, which is why the trap comes only for lists and dictionaries.

List and dictionary, two different jobs

Neither is better than the other; the question is whether your things have names or numbers.

List

  • Things of one kind: names, prices, files
  • Order means something and is kept
  • You call them by number, and counting starts at zero
  • Built for "take them one at a time"

Dictionary

  • Different things, each with a name of its own
  • Order does not matter; the key does
  • You call them by key, such as city or price
  • Built for "the properties of one thing"

Both are mutable, so the two labels on one value trap applies to both, and copying has to be explicit for both.

When the type is wrong and no error appears

Up to here every type error made a noise. Now for the family that makes none, and is more expensive for exactly that reason. Two examples from this site, both checkable in our own code.

The first. Every piece of text in this learning section has to be a four language array, that is a data structure with four keys. Once, in the AI section of this site, a plain string was written instead of an array. Nothing raised an error; a string is a value too and it prints. The result was that the English page printed Persian text. The mistake was not in the value, it was in the type. Now the validator of this section checks exactly that before publication, and when it sees a string it says it must be a four language array and reports the type it got.

The second is more instructive. The theme of this site turns numeric table columns into bars, and to do that it has to read a number written in the text. The first rule was that a comma is a thousands separator. On the Turkish page, "2,6 MB" became twenty six and "80,6 per cent" became eight hundred and six, and a bar was drawn eight times longer than what the page itself said. The cause was our assumption: Turkish uses the dot for thousands and the comma for decimals, exactly the other way round from Persian and English.

The solution finally chosen does not ask for the language; it looks at the size of the groups. A group of exactly three digits is thousands and anything else is a decimal, with one exception: a number starting with zero is always a decimal. What matters here is that neither of those two bugs raised an error. The program ran happily and printed a wrong number.

The lesson we took from both, and pass on: check the type at the boundary, meaning the place where data enters the program. User input, a file you read, an answer you get from a service. If you do not control the type there, wrong data travels deep into the program, and by then nobody can tell where it came from.

The fast path, with AI

The place where data types really cost time is a real file: an exported spreadsheet, a CSV, an answer coming back from a service. The fast path is not having the model clean the data for you, because it cannot clean data it never sees. The fast path is getting a type map out of it, then the list of values that break that map, and testing that list yourself against the whole file. A fast cheap model is enough for this; our current pick is in <a class="text-link" href="/en/ai/">the AI section</a>.

  1. Paste five real rows of your own data, not a description of it. A description is what you think is in the file; a row is what is actually there.
  2. Ask for the type and the exact conversion for every column, with a reason. Where the reason is weak is usually where a wrong assumption lives.
  3. Ask for the list of values that break each conversion: empty, non Latin digits, a decimal comma, a leading zero, a negative number, an extra space.
  4. Run the conversion over the whole file and count the failures. Eyeballing the first five rows is exactly what we did with the Turkish table, and it came out eight times wrong.

Copy-ready recipe

These are five real rows from my file, untouched:

{five real rows, with the header}

This data is going to be used for {what you want to do with it}.

Give three things and only these three:

1. A type table: for each column, the proposed type, the exact conversion
   function, and a one sentence reason. Say explicitly which columns must stay
   strings and why.
2. For each column, the values that break its conversion. Include at least:
   an empty cell, non Latin digits, a comma used as a decimal point, a leading
   zero, a negative number, and extra spaces at the start and end.
3. Conversion code that raises an error and prints the row when it fails. Do
   not swallow the error with a bare try and do not put a default value in the
   place of broken data.

Do not assume a comma is a thousands separator. If you are not sure, ask.

Before you trust the output: The model saw five rows, not fifty thousand. The breakages live exactly in the rows you did not paste, so the result of this exercise is a map and not a guarantee. Two things you should never do: do not run cleaning code on the original file, run it on a copy and compare the row counts before and after; and if the code you were given swallows errors and inserts a default, change that. A bug that raises an error costs an hour, and a bug that quietly makes a wrong number costs months.

AI in this kind of work

For data types, the best thing a model does is translate an error message into your own words and find the values that break your code. The worst thing it does is convert your data quietly while you are not looking. Our position follows: the conversion belongs in your code and in front of your eyes, not inside an answer you pasted.

Tools that actually help

  • Claude Good at "what does this error mean" and at building the list of values that break a conversion. Iran is not on Anthropic supported countries list, which we read on Anthropic own page.
  • Gemini Understands Persian well, so when your data is Persian and carries Persian digits it is easier to explain the problem to it. Google own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.
  • ChatGPT The most common choice and acceptable for this job. We do not have a page for it in our AI section yet, so we make no claim here about its access or pricing.

Where it backfires

There is a habit in model answers that builds exactly the thing this lesson warns about: it wraps the conversion in a try and returns a default when it fails, usually zero. The program stops raising errors and you are pleased, but from that moment every broken row in your file has turned into a zero and taken its seat in the final total. If you carry one rule out of this section, let it be this: write in the prompt that on failure it must raise and print the row. The second risk is the local assumption: unless you say otherwise the model usually takes the English rule and reads a comma as a thousands separator, the same assumption that produced an eight times error on our own Turkish table. State the language and the number format of your data explicitly. And the third risk is access: Iran is on neither of Anthropic two supported countries lists and is not on Google list of countries for Gemini; we read both on the vendor own pages and we do not suggest routes around them.

Sources: Python docs: built-in types Anthropic: supported countries Google: where Gemini Apps are available

Where this advice stops

Python checks types at run time, meaning nothing tells you before the run that you got a type wrong. Statically typed languages catch it earlier, and that is a real difference this lesson has no room for. Three things are also deliberately absent: classes and types you build yourself, the details of the decimal module, and database types, which have rules of their own. And one point that gets forgotten: knowing the types does not prevent wrong data. The only thing that prevents it is running the program against real data rather than three sample rows.

From our own work

The first time the numeric table columns on this site were turned into bars, the test landed on the Core Web Vitals table, and the result is today lesson. In one column sat "2.5 seconds", "200 milliseconds" and "0.1". The code had read the numbers correctly, yet the bars said INP was eighty times LCP, a sentence that page had never made. The problem was not the type, it was the unit: 2.5 and 200 are both numbers and they are not comparable until their unit is the same. The rule we put in afterwards is still in place: a column gets bars only when the leftover text of all its cells, meaning their unit, is exactly identical. A number without its unit is not yet a complete value.

Real follow-up questions

How do I find out what is inside a variable?

print(type(x)) shows you its type and print(x) shows you the thing itself. Add those two lines wherever you are unsure and delete them afterwards; that is the simplest and most used debugging tool in Python. If the answer is <class 'str'> when you expected a number, you have found the problem right there.

Python works out the type by itself, so why should I care?

Because it works it out but does not stand guard. Python knows the type at the moment you create the value, yet nothing tells you that you got it wrong until the program runs. That means the error arrives at run time, usually when a real user has entered real data. Checking the type at the input boundary is therefore your job and not the language job.

Should I learn type hints right away?

Not in the first days. Type hints earn their keep in large code and in teamwork, and add nothing to a twenty line program. Know one thing so that you do not expect the wrong thing from them: Python own documentation says the runtime does not enforce these annotations and that outside tools such as type checkers use them. Writing age: int does not stop a string from arriving.