InterviewPrepKit

Home / Learn / Algorithms & Data Structures with Python

Numbers, Strings, and Booleans

What this lesson is about

Every program moves data around. Before you can solve problems, you need to know the basic kinds of data Python gives you and what you can do with each. This lesson covers three of them:

  • Numbers — for counting and calculating.
  • Strings — for text.
  • Booleans — for yes/no, true/false answers that drive decisions.

A value is a single piece of data, like the number 7 or the text "hello". A type is the category a value belongs to. Python always knows the type of every value, and the type decides what operations make sense. You cannot divide the word "cat" by 2, but you can divide the number 10 by 2.

You can ask Python for the type of any value with the built-in type function. A function is a named piece of behavior you can run; you “call” it by writing its name followed by parentheses with an input inside.

print(type(7))        # -> <class 'int'>
print(type(3.5))      # -> <class 'float'>
print(type("hello"))  # -> <class 'str'>
print(type(True))     # -> <class 'bool'>

print is a function that displays a value on the screen. Throughout this lesson, a comment starting with # -> shows what a line produces.

Numbers: int vs float

Python has two everyday number types.

  • int (short for integer) is a whole number with no fractional part: -3, 0, 42. Python integers can be as large as you like; they never overflow.
  • float (short for floating-point number) is a number with a decimal point: 3.5, -0.001, 2.0. Floats are how computers approximate real numbers.
count = 42        # int
price = 3.99      # float
print(type(count))  # -> <class 'int'>
print(type(price))  # -> <class 'float'>

The word count here is a variable: a name that refers to a value. The = sign is assignment; it stores the value on the right into the name on the left. It is not “equals” in the math sense (that comes later with ==).

Writing 2.0 instead of 2 matters to the type. 2 is an int; 2.0 is a float, even though they represent the same quantity.

Arithmetic operators

An operator is a symbol that combines values. The basic arithmetic operators:

print(7 + 3)   # -> 10   addition
print(7 - 3)   # -> 4    subtraction
print(7 * 3)   # -> 21   multiplication
print(7 / 3)   # -> 2.3333333333333335   true division, always a float

Note that / always produces a float, even when the result is whole:

print(6 / 2)   # -> 3.0   (a float, not 3)

Three more operators are worth knowing well, because they show up constantly in real problems.

Floor division // divides and throws away the fractional part, rounding down to the nearest whole number.

print(7 // 3)   # -> 2    (7/3 is 2.33..., rounded down)
print(8 // 2)   # -> 4
print(-7 // 3)  # -> -3   (rounds down, toward negative infinity)

Remainder % (called modulo) gives what is left over after floor division.

print(7 % 3)    # -> 1    (7 = 2*3 + 1, remainder 1)
print(10 % 2)   # -> 0    (10 is even, no remainder)
print(10 % 3)   # -> 1

% is the standard way to test whether a number is even or divisible by something: n % 2 == 0 is true exactly when n is even.

Power ** raises a number to an exponent.

print(2 ** 3)   # -> 8    (2 * 2 * 2)
print(5 ** 2)   # -> 25
print(9 ** 0.5) # -> 3.0  (a fractional power is a root; this is the square root)

If both operands are ints, + - * // % ** keep the result an int (except /, which is always float). If either operand is a float, the result is a float.

print(3 + 4)     # -> 7      (int)
print(3 + 4.0)   # -> 7.0    (float, because one side is float)

Float rounding pitfalls

Floats are approximations. A computer stores them in binary (base 2), and many ordinary decimal fractions cannot be written exactly in binary, the same way 1/3 cannot be written exactly in decimal. The result is small surprises:

print(0.1 + 0.2)          # -> 0.30000000000000004
print(0.1 + 0.2 == 0.3)   # -> False

This is not a bug in Python; every language using standard floats behaves this way. Two practical rules:

  • Do not compare floats with == for exact equality. Instead check that they are close enough:
a = 0.1 + 0.2
print(abs(a - 0.3) < 1e-9)   # -> True

abs gives the absolute value (distance from zero), and 1e-9 is scientific notation for 0.000000001, a tiny tolerance.

  • To display a rounded number, use the round function, which takes the value and how many decimal places you want:
print(round(3.14159, 2))   # -> 3.14
print(round(2.5))          # -> 2   (rounds to nearest even; see pitfalls below)

Strings

A string is text: a sequence of characters such as letters, digits, spaces, and punctuation. You create one by wrapping characters in quotes. Single and double quotes both work; pick one and be consistent.

name = "Ada"
greeting = 'hello'
empty = ""          # a string with zero characters

Length and indexing

len returns how many characters a string contains.

print(len("hello"))   # -> 5
print(len(""))        # -> 0

Indexing means reading one character by its position. Positions start at 0, not 1. So in "hello", h is at index 0 and o is at index 4. You index with square brackets.

word = "hello"
print(word[0])   # -> h
print(word[4])   # -> o

Negative indexes count from the end: -1 is the last character.

print(word[-1])  # -> o
print(word[-2])  # -> l

Asking for an index that does not exist raises an error (a crash with a message):

# word[10]  ->  IndexError: string index out of range

Every character in "hello" has two addresses at once — a positive index counting up from the front and a negative one counting back from the end:

graph LR
  A["'h'\nindex 0\nindex -5"] --- B["'e'\nindex 1\nindex -4"] --- C["'l'\nindex 2\nindex -3"] --- D["'l'\nindex 3\nindex -2"] --- E["'o'\nindex 4\nindex -1"]

Slicing

Slicing pulls out a range of characters. The syntax is s[start:stop], and it includes start but excludes stop. This “up to but not including” rule is everywhere in Python; get comfortable with it early.

s = "programming"
print(s[0:4])   # -> prog   (indexes 0, 1, 2, 3)
print(s[4:7])   # -> ram

Leaving out a side means “from the beginning” or “to the end”:

print(s[:4])    # -> prog
print(s[4:])    # -> ramming
print(s[:])     # -> programming   (a full copy)

f-strings: building text from values

Often you want to insert a value into text. An f-string (the f stands for formatted) does this. Put f before the opening quote, then write {...} around any value or variable you want to drop in.

name = "Ada"
age = 36
print(f"{name} is {age} years old")   # -> Ada is 36 years old
print(f"next year: {age + 1}")        # -> next year: 37

Without f-strings you would have to convert numbers to text and join pieces by hand, which is more error-prone. f-strings are the standard, readable choice.

Common string methods

A method is a function attached to a value, called with a dot: value.method(). Strings come with many useful ones. Here are the ones you will reach for constantly.

text = "  Hello World  "

print(text.upper())            # -> "  HELLO WORLD  "
print(text.lower())            # -> "  hello world  "
print(text.strip())            # -> "Hello World"   (removes surrounding spaces)
print(text.replace("l", "L"))  # -> "  HeLLo WorLd  "

.split() breaks a string into a list of pieces. A list is an ordered collection of values, written with square brackets and commas. By default .split() cuts on spaces; you can pass a different separator.

"a,b,c".split(",")     # -> ['a', 'b', 'c']
"one two three".split() # -> ['one', 'two', 'three']

The in operator tests whether one string appears inside another. It gives back a boolean (covered next).

print("ell" in "hello")   # -> True
print("z" in "hello")     # -> False

Strings are immutable

Immutable means “cannot be changed after it is created.” You cannot alter a single character of an existing string in place:

s = "cat"
# s[0] = "b"   ->  TypeError: 'str' object does not support item assignment

Instead, string operations always build a new string and leave the original alone:

s = "cat"
t = s.replace("c", "b")
print(s)   # -> cat   (unchanged)
print(t)   # -> bat   (a new string)

This matters for performance. Because a string cannot be edited in place, building a long string by repeatedly adding pieces in a loop copies the whole thing each time, which is O(n^2) total work for n characters. Big-O is a way to describe how the cost of an operation grows with the size of the input; O(n^2) means the work grows with the square of the length, which gets slow quickly. The efficient pattern is to collect the pieces in a list and join them once with "".join(...), which is O(n).

parts = ["a", "b", "c"]
print("".join(parts))   # -> abc

Booleans

A boolean is a value that is either True or False. Booleans are the answers to yes/no questions, and they drive every decision a program makes. There are exactly two boolean values, and both are capitalized.

print(True)          # -> True
print(type(False))   # -> <class 'bool'>

Comparison operators

Comparisons ask a question about two values and give back a boolean.

print(3 == 3)   # -> True    equal to
print(3 != 4)   # -> True    not equal to
print(3 < 4)    # -> True    less than
print(5 > 4)    # -> True    greater than
print(3 <= 3)   # -> True    less than or equal to
print(5 >= 6)   # -> False   greater than or equal to

Note the difference between = and ==. A single = assigns a value to a variable. A double == compares two values and answers whether they are equal. Mixing these up is one of the most common beginner mistakes.

Comparisons work on strings too, alphabetically (technically by character code):

print("apple" < "banana")   # -> True
print("Ada" == "ada")       # -> False   (case matters)

Logical operators: and, or, not

These combine or flip booleans so you can express compound conditions.

  • and is True only when both sides are True.
  • or is True when at least one side is True.
  • not flips a boolean to its opposite.
print(True and False)   # -> False
print(True or False)    # -> True
print(not True)         # -> False

age = 25
print(age >= 18 and age < 65)   # -> True

Truthiness

Python lets you use non-boolean values where a true/false answer is expected. Each value is treated as either truthy (acts like True) or falsy (acts like False). The falsy values you will meet most are:

  • the number 0 (and 0.0)
  • the empty string ""
  • the empty list []
  • the special value None (which means “no value”)

Almost everything else is truthy. You can check by passing a value to bool:

print(bool(0))     # -> False
print(bool(42))    # -> True
print(bool(""))    # -> False
print(bool("hi"))  # -> True
print(bool([]))    # -> False
print(bool([1]))   # -> True

This is why code often reads if items: instead of if len(items) > 0:. An empty list is falsy, so the shorter form means the same thing and is idiomatic Python.

Common pitfalls

  • = vs ==. x = 5 stores 5 into x. x == 5 asks whether x equals 5. Using = where you meant == is a frequent bug.
  • Comparing floats with ==. Because floats are approximate, 0.1 + 0.2 == 0.3 is False. Compare with a tolerance instead.
  • / is always a float. 6 / 2 is 3.0, not 3. Use // when you need an int result from division.
  • Indexes start at 0. The first character is at index 0, and the last valid index of a length-n string is n - 1. Going past the end raises IndexError.
  • Slices exclude the stop. s[0:4] gives characters 0,1,2,3 — four of them, not five.
  • Strings are immutable. s[0] = "x" is an error. Build a new string instead.
  • round uses banker’s rounding. round(2.5) is 2 and round(3.5) is 4: ties round to the nearest even number, not always up. This surprises people who expect 2.5 to become 3.

Practice

  1. Given n = 47, print whether it is even or odd using %, and print its last digit (also using %).
  2. Take the string s = " Data Driven ". Produce a lowercase version with the surrounding spaces removed, then print how many characters that cleaned string has.
  3. Write a single boolean expression that is True when a variable score is between 0 and 100 inclusive, and False otherwise. Test it with a few values.
Report a bug