# Arrays

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Reference Types
Source: https://www.devtutorials.ai/learn/python/reference-types/arrays

> The array module: type codes, indexing from both ends, inserting and removing, searching, every array method, enumerate() and the numeric built-ins.

Think about an egg tray.

It holds twelve eggs. Every slot is the same size and the same shape, because every slot holds the same kind of thing. You find the fourth egg by counting along, not by searching.

That is an **array**: many values of one kind, kept in order, each reachable by its position.

## What You Will Learn

In this lesson, you will learn:

- What an array is, and what Python uses instead most of the time
- How to create one, and what the type code means
- How to reach any element, counting from the front or the back
- How to change, add and remove elements
- How to search an array
- The methods an array carries with it
- How `enumerate()` gives you the position and the value together
- The numeric built-ins you will use on arrays every day

By the end of this lesson, you will be able to build arrays, reach and change any element by position, add and remove values, and use the built-in functions to work out totals, averages, highest and lowest values.

Even though Python programmers usually use lists, understanding arrays helps you learn the core ideas behind indexed collections.

## Arrays

### Introduction to Arrays

An **array** is a collection of values of the same type, stored one after another in memory, each identified by a number called its **index**.

Because every element is the same size, the computer can jump straight to any position without looking at the ones before it. That is why `marks[500]` is just as fast as `marks[0]`.

Here is that idea in Python. Three marks are held under one name, and `marks[0]` is the first one, because positions start at 0:

```python runnable title="Three marks in one name"
from array import array

marks = array("i", [78, 85, 60])

print(marks)
print(marks[0])
print(len(marks))
```

An array holds many values of the same kind, in order, each reachable by its position.

The entire collection is stored in one variable, and each value can be reached by its position.

### Arrays in Python

Here is the part that confuses people coming from other languages.

In Java or C you reach for an array constantly. **In Python you almost always use a list instead.**

```python runnable title="A list does the same job, with no import"
marks = [78, 85, 60]

print(marks)
print(marks[0])
print(len(marks))
```

A list is built in, takes any mix of types, and has more methods. It is what Python programmers use for a collection of marks.

The `array` module exists for one reason: **storing a lot of numbers compactly**. An array of a million whole numbers uses far less memory than a list of a million whole numbers, because it stores the raw numbers rather than full Python objects.

```python runnable title="The same values, two different costs"
import sys
from array import array

list_marks = [78, 85, 60, 92, 45]
array_marks = array("i", [78, 85, 60, 92, 45])

print("List holds:", list_marks)
print("Array holds:", array_marks)
print("Bytes per number in the array:", array_marks.itemsize)
print("Whole array, in bytes:", array_marks.buffer_info()[1] * array_marks.itemsize)
print("List object alone, in bytes:", sys.getsizeof(list_marks))
```

The array stores five numbers in 20 bytes. The list needs that much just for its own structure, before counting the five number objects it points at.

So: use a list unless you are holding a great many numbers and memory matters. Learn arrays anyway, because the ideas — index, traverse, insert, delete, search — are the same ones every collection uses.

### Array Characteristics

An array has a small set of rules, and every one of them shapes how you use it:

<div className="tscroll">

| Characteristic | What it means |
|---|---|
| Same type | every element must match the type code |
| Ordered | elements keep the order you put them in |
| Indexed | the first element is at 0 |
| Changeable | you can replace, add and remove elements |
| Compact | the raw numbers are stored, not Python objects |

</div>

The "same type" rule is the real difference from a list, and it is enforced:

```python runnable expect-error title="An array refuses the wrong type"
from array import array

marks = array("i", [78, 85])
marks.append(7.5)
```

`TypeError` — `i` means whole numbers, and 7.5 is not one. A list would have accepted it without a word.

## Creating Arrays

### Importing the array Module

Arrays are not built in. The `array` module ships with Python, so there is nothing to install.

```python runnable title="Two ways to import it"
import array

a = array.array("i", [78, 85])
print(a)

from array import array

b = array("i", [60, 92])
print(b)
```

The second form is what you will usually see, because `array.array(...)` reads oddly.

### Creating Arrays with array()

Making an array is a single call:

```text
array(typecode, values)
```

The type code is a single letter saying what kind of value the array holds. It is not optional.

```python runnable title="The two things an array needs"
from array import array

marks = array("i", [78, 85, 60])

print(marks)
print("Type code:", marks.typecode)
print("Bytes each:", marks.itemsize)
```

An array can start empty and be filled later:

```python runnable title="Starting empty"
from array import array

marks = array("i")

print("Empty:", marks, len(marks))

marks.append(78)
marks.append(85)

print("Filled:", marks, len(marks))
```

### Integer Arrays

`i` holds whole numbers, taking 4 bytes each.

```python runnable title="Whole numbers"
from array import array

marks = array("i", [78, 85, 60, 92])

print(marks)
print("Total:", sum(marks))
print("Highest:", max(marks))
```

### Float Arrays

`d` holds decimal numbers, taking 8 bytes each. `f` is the smaller, less precise version.

```python runnable title="Decimal numbers"
from array import array

averages = array("d", [78.5, 85.25, 60.75])

print(averages)
print("Average of averages:", round(sum(averages) / len(averages), 2))
print("Bytes each:", averages.itemsize)
```

Putting a whole number into a `d` array is fine — it is widened, exactly as `7 + 2.5` is:

```python runnable title="A whole number in a decimal array"
from array import array

averages = array("d", [78.5])
averages.append(85)

print(averages)
```

### Character Arrays

The `array` module has a type code `u` for single characters.

```python runnable title="An array of characters"
from array import array

name = array("u", "Asha")

print(name)
print(name[0])
print(name.tounicode())
```

Be careful with this one. **The `u` type code is deprecated**, which means it still works today but is on its way out of Python. For text, use a plain string, and for a collection of words use a list of strings.

```python runnable title="What to use for text instead"
name = "Asha"
subjects = ["Maths", "Science", "English"]

print(name[0])
print(subjects[0])
print(len(name), len(subjects))
```

Strings and lists support the same indexing you are learning here, so nothing is lost.

### Array Type Codes

The codes are one letter each, and each letter fixes the size of one element:

<div className="tscroll">

| Code | Holds | Bytes | Example |
|---|---|---|---|
| `b` | small whole number, signed | 1 | `array("b", [-5, 5])` |
| `B` | small whole number, no minus | 1 | `array("B", [0, 255])` |
| `i` | whole number | 2 or more | `array("i", [78])` |
| `I` | whole number, no minus | 2 or more | `array("I", [78])` |
| `l` | bigger whole number | 4 or more | `array("l", [100000])` |
| `q` | very big whole number | 8 | `array("q", [10 ** 15])` |
| `f` | decimal, less precise | 4 | `array("f", [78.5])` |
| `d` | decimal | 8 | `array("d", [78.5])` |
| `u` | one character (deprecated) | 2 or 4 | `array("u", "Asha")` |

</div>

```python runnable title="Different codes, different sizes"
from array import array

for code in ["b", "i", "l", "q", "f", "d"]:
    sample = array(code, [1])
    print(code, "->", sample.itemsize, "bytes each")
```

Choose the smallest code that fits your numbers. A code too small will refuse the value rather than wrap around quietly:

```python runnable expect-error title="A number too big for its code"
from array import array

small = array("b", [127])
small.append(200)
```

`OverflowError` — `b` holds up to 127, and 200 does not fit.

Once an array has been created, the next step is learning how to read values from it.

## Accessing Array Elements

### Array Indexing

An **index** is the position of an element. Python counts from 0, so the first element is at index 0.

#### Positive Indexing

```python runnable title="Counting from the front"
from array import array

marks = array("i", [78, 85, 60, 92])

print("First:", marks[0])
print("Second:", marks[1])
print("Last:", marks[3])
```

With four elements, the valid indexes are 0, 1, 2 and 3. The last index is always one less than the length, which is the single most common off-by-one in programming.

```python runnable expect-error title="One step past the end"
from array import array

marks = array("i", [78, 85, 60, 92])

print(marks[4])
```

`IndexError: array index out of range`.

#### Negative Indexing

A negative index counts from the back. `-1` is the last element.

```python runnable title="Counting from the back"
from array import array

marks = array("i", [78, 85, 60, 92])

print("Last:", marks[-1])
print("Second from last:", marks[-2])
print("First:", marks[-len(marks)])
```

`marks[-1]` is much safer than `marks[len(marks) - 1]`, and says the same thing.

Slicing works too, taking a range of positions:

```python runnable title="Taking a slice"
from array import array

marks = array("i", [78, 85, 60, 92, 45])

print(marks[1:3])
print(marks[:2])
print(marks[2:])
print(marks[::-1])
```

A slice goes up to but not including the second number, exactly like `range()`.

### Traversing Arrays

**Traversing** means visiting every element in turn.

```python runnable title="Visiting every element"
from array import array

marks = array("i", [78, 85, 60])

for mark in marks:
    print("Mark:", mark)
```

That is the form to reach for. Looping over the indexes is possible, but only worth it when you actually need the position:

```python runnable title="Looping over the positions instead"
from array import array

marks = array("i", [78, 85, 60])

for position in range(len(marks)):
    print(position, "->", marks[position])
```

Reading values is only half the story. Arrays are mutable, which means you can change their contents after creation.

## Modifying Arrays

### Updating Array Elements

Assign to a position to replace what is there.

```python runnable title="Replacing one element"
from array import array

marks = array("i", [78, 85, 60])

print("Before:", marks)

marks[2] = 75

print("After:", marks)
```

The array did not grow. One value was swapped for another.

### Inserting Elements

`insert(position, value)` puts a value **before** the given position, and everything after it shifts along.

```python runnable title="Inserting in the middle"
from array import array

marks = array("i", [78, 85, 60])

marks.insert(1, 100)

print(marks)
```

100 is now at index 1, and 85 and 60 have each moved one place right.

```python runnable title="Inserting at the front"
from array import array

marks = array("i", [78, 85])

marks.insert(0, 50)

print(marks)
```

Inserting at the front is the slowest thing you can do to an array, because every other element has to move. Adding at the end moves nothing.

### Appending Elements

`append(value)` adds one value to the end.

```python runnable title="Adding to the end"
from array import array

marks = array("i", [78, 85])

marks.append(60)
marks.append(92)

print(marks)
print("Length now:", len(marks))
```

## Removing Array Elements

### Using remove()

`remove(value)` deletes the **first** element equal to that value. It works by value, not by position.

```python runnable title="Removing by value"
from array import array

marks = array("i", [78, 85, 60, 85])

marks.remove(85)

print(marks)
```

Only the first 85 went. The second is still there.

If the value is not present, Python stops:

```python runnable expect-error title="Removing something that is not there"
from array import array

marks = array("i", [78, 85])

marks.remove(99)
```

`ValueError` — check with `in` first when you are not sure.

### Using pop()

`pop()` removes an element **by position** and hands it back to you.

```python runnable title="Removing by position, and keeping the value"
from array import array

marks = array("i", [78, 85, 60])

last = marks.pop()
first = marks.pop(0)

print("Removed from the end:", last)
print("Removed from the front:", first)
print("What is left:", marks)
```

With no number, `pop()` takes the last element. With a number, it takes that position.

Here are the two side by side, so the difference stands out:

<div className="tscroll">

| | `remove()` | `pop()` |
|---|---|---|
| Works by | value | position |
| Gives back | nothing | the removed element |
| Not found | `ValueError` | `IndexError` |
| Use when | you know the value | you know the position, or want the value |

</div>

After learning how to add, remove and change values, the next skill is finding them.

## Searching in Arrays

### Finding Elements with index()

`index(value)` gives the position of the first element equal to that value.

```python runnable title="Finding a position"
from array import array

marks = array("i", [78, 85, 60, 85])

print(marks.index(85))
print(marks.index(60))
```

It raises `ValueError` when the value is missing, so test first:

```python runnable title="Looking before leaping"
from array import array

marks = array("i", [78, 85, 60])

if 99 in marks:
    print("Found at", marks.index(99))
else:
    print("99 is not in the array")
```

### Membership Testing

`in` answers whether a value is present, with no error and no position.

```python runnable title="Just asking whether it is there"
from array import array

marks = array("i", [78, 85, 60])

print(78 in marks)
print(99 in marks)
print(99 not in marks)
```

Use `in` when you want a yes or no, and `index()` when you need to know where.

## Array Operations

These five operations are what every collection is judged on, in any language.

### Traversing

Visiting every element once.

```python runnable title="Traversal, and what it costs"
from array import array

marks = array("i", [78, 85, 60, 92])

total = 0
for mark in marks:
    total += mark

print("Total:", total)
print("Elements visited:", len(marks))
```

Traversal always costs one step per element.

### Insertion

There are two ways to add, and they behave differently:

```python runnable title="Insertion at the end and in the middle"
from array import array

marks = array("i", [78, 85, 60])

marks.append(92)
print("After append:", marks)

marks.insert(1, 100)
print("After insert at 1:", marks)
```

Appending is cheap. Inserting near the front means shifting everything after it.

### Deletion

```python runnable title="Three ways to delete"
from array import array

marks = array("i", [78, 85, 60, 92])

marks.remove(85)
print("After remove(85):", marks)

marks.pop(0)
print("After pop(0):", marks)

del marks[0]
print("After del marks[0]:", marks)
```

`del` removes by position and gives nothing back, which is the difference from `pop()`.

### Search

```python runnable title="Searching from front to back"
from array import array

marks = array("i", [78, 85, 60, 92])
target = 60

for position, mark in enumerate(marks):
    if mark == target:
        print("Found", target, "at position", position)
        break
else:
    print(target, "is not here")
```

This is a **linear search**: look at each element until you find it. The worst case checks every element.

### Update

```python runnable title="Changing one element, and all of them"
from array import array

marks = array("i", [78, 85, 60])

marks[0] = 80
print("One changed:", marks)

for position in range(len(marks)):
    marks[position] += 5

print("All raised by 5:", marks)
```

Changing one element is instant — Python jumps straight to that position.

## Array Methods

An array carries its own set of methods. To add elements you have three choices, each for a different job.

### append()

Adds one element to the end.

```python runnable title="append"
from array import array

marks = array("i", [78])
marks.append(85)
print(marks)
```

If you `append` a tuple or a list, the whole thing is added as one element.

### extend()

Adds **several** elements to the end.

```python runnable title="extend, and how it differs from append"
from array import array

a = array("i", [78, 85])
a.extend([60, 92])
print("extend:", a)

b = array("i", [78, 85])
b.append(60)
print("append:", b)
```

`append` adds one thing. `extend` adds each item of a collection.

### insert()

```python runnable title="insert"
from array import array

marks = array("i", [78, 60])
marks.insert(1, 85)
print(marks)
```

### remove()

```python runnable title="remove"
from array import array

marks = array("i", [78, 85, 60])
marks.remove(85)
print(marks)
```

### pop()

```python runnable title="pop"
from array import array

marks = array("i", [78, 85, 60])
print("Taken:", marks.pop())
print("Left:", marks)
```

### index()

```python runnable title="index"
from array import array

marks = array("i", [78, 85, 60])
print(marks.index(60))
```

### reverse()

Reverses the array **in place** — it changes the array and gives nothing back.

```python runnable title="reverse changes the array itself"
from array import array

marks = array("i", [78, 85, 60])

result = marks.reverse()

print("The array now:", marks)
print("What reverse() returned:", result)
```

`result` is `None`. Writing `marks = marks.reverse()` would throw the array away — a mistake worth meeting once.

### tolist()

Converts the array into an ordinary list.

```python runnable title="tolist"
from array import array

marks = array("i", [78, 85, 60])
as_list = marks.tolist()

print(as_list, type(as_list).__name__)

as_list.append("absent")
print("A list accepts anything:", as_list)
```

Use it when you need something a list can do and an array cannot — mixing types, or handing the values to code that expects a list.

Here is the full set in one table:

<div className="tscroll">

| Method | What it does | Gives back |
|---|---|---|
| `append(v)` | adds one element at the end | nothing |
| `extend(values)` | adds each item of a collection | nothing |
| `insert(i, v)` | inserts before position `i` | nothing |
| `remove(v)` | deletes the first element equal to `v` | nothing |
| `pop(i)` | deletes by position | the removed element |
| `index(v)` | position of the first `v` | a number |
| `reverse()` | reverses in place | nothing |
| `tolist()` | copies into a list | a new list |

</div>

Most array methods change the array and return `None`. `marks = marks.reverse()` leaves you holding `None` with the array gone. Call it on its own line.

## enumerate() Function

### Getting Index and Value

Often you want both the position and the value. Doing it by hand needs a counter:

```python runnable title="Keeping a counter by hand"
from array import array

marks = array("i", [78, 85, 60])

position = 0
for mark in marks:
    print(position, mark)
    position += 1
```

`enumerate()` does that for you, and cannot forget to add 1:

```python runnable title="enumerate does it for you"
from array import array

marks = array("i", [78, 85, 60])

for position, mark in enumerate(marks):
    print(position, mark)
```

### Using enumerate() in Loops

`start=` changes the first number, which is how you number things for people:

```python runnable title="Numbering from 1 for a report"
from array import array

marks = array("i", [78, 85, 60])
subjects = ["Maths", "Science", "English"]

for number, mark in enumerate(marks, start=1):
    print(number, subjects[number - 1], mark)
```

`enumerate()` works on anything you can loop over, not just arrays:

```python runnable title="The same tool on a string and a list"
for position, letter in enumerate("Asha"):
    print(position, letter)

for position, subject in enumerate(["Maths", "Science"]):
    print(position, subject)
```

Reach for `enumerate()` whenever you catch yourself writing `range(len(...))`. It is shorter, and the counter cannot drift out of step with the values.

Arrays work well with many of Python's built-in numeric functions. These functions can calculate totals, averages and other useful values without writing the loops yourself.

## Numeric Functions

These are built in. They work on arrays, lists, and anything else you can loop over.

### sum()

`sum()` adds up everything in a collection:

```python runnable title="sum"
from array import array

marks = array("i", [78, 85, 60])

print(sum(marks))
print(sum(marks) / len(marks))
print(sum(marks, 100))
```

The second number is where the total starts, which is occasionally useful and often surprising.

### min() and max()

`min()` and `max()` find the ends of a collection, and both sides of a comparison:

```python runnable title="min and max"
from array import array

marks = array("i", [78, 85, 60])

print("Lowest:", min(marks))
print("Highest:", max(marks))
print("Range:", max(marks) - min(marks))
```

They work on text too, comparing character by character:

```python runnable title="min and max on text"
subjects = ["Maths", "Science", "English"]

print(min(subjects))
print(max(subjects))
print(max("Asha"))
```

`max("Asha")` is `s`, not `A`. Capital letters come before small ones in character order, so `s` is the largest character here.

### round()

```python runnable title="round"
from array import array

averages = array("d", [78.456, 85.5, 60.249])

print(round(78.456, 2))
print(round(sum(averages) / len(averages), 1))
print(round(78.456))
```

With no second number, `round()` gives a whole number.

### abs()

`abs()` gives the distance from zero, dropping any minus sign.

```python runnable title="abs"
marks_change = [-12, 5, -3]

for change in marks_change:
    print(change, "-> moved by", abs(change))
```

Useful whenever you care how much something changed, not which way.

### pow()

`pow(x, y)` is x to the power of y — the same as `x ** y`.

```python runnable title="pow"
print(pow(2, 3))
print(2 ** 3)
print(pow(144, 0.5))
print(pow(2, 10, 1000))
```

The third number is a remainder step, used in cryptography. Treat it as a curiosity for now.

Here are all six in one table:

<div className="tscroll">

| Function | What it does | Example | Result |
|---|---|---|---|
| `sum(values)` | adds them up | `sum([78, 85])` | `163` |
| `min(values)` | smallest | `min([78, 85])` | `78` |
| `max(values)` | largest | `max([78, 85])` | `85` |
| `round(x, n)` | rounds to `n` places | `round(78.456, 1)` | `78.5` |
| `abs(x)` | distance from zero | `abs(-12)` | `12` |
| `pow(x, y)` | x to the power of y | `pow(2, 3)` | `8` |

</div>

```python runnable title="A small report built from the lot"
from array import array

marks = array("i", [78, 85, 60, 92, 45])

print("Count:   ", len(marks))
print("Total:   ", sum(marks))
print("Average: ", round(sum(marks) / len(marks), 1))
print("Lowest:  ", min(marks))
print("Highest: ", max(marks))
print("Spread:  ", abs(max(marks) - min(marks)))
```

## Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Using the length as the last index | `IndexError: array index out of range` | The last index is one less than the length | `marks[-1]`, or `marks[len(marks) - 1]` |
| Putting the wrong type in | `TypeError: 'float' object cannot be interpreted as an integer` | The type code is enforced | Match the code, or use `d` for decimals |
| A number too big for its code | `OverflowError` | `b` holds up to 127 | Use a wider code such as `i` or `q` |
| Assigning the result of `reverse()` | The array becomes `None` | It changes the array in place and returns nothing | Call it on its own line |
| `remove()` for a value that is absent | `ValueError` | `remove()` works by value and needs it present | Check with `in` first |
| Confusing `remove()` with `pop()` | The wrong element disappears | `remove()` takes a value, `pop()` a position | Pick by what you actually know |
| `append()` where `extend()` was meant | A collection added as one element | `append` adds exactly one thing | `extend` to add each item |
| Expecting `remove()` to delete every copy | Only the first one goes | It removes the first match | Loop, or rebuild the array |
| Forgetting the type code | `TypeError: array() argument 1 must be a str` | The code is not optional | `array("i", [...])` |
| Changing an array while looping over it | Elements quietly skipped | The positions shift under the loop | Loop over a copy, or build a new one |

</div>

## What to Remember

- **An array holds many values of the same kind, in order, each reachable by its position.**
- In Python you normally use a **list**. The `array` module is for storing a great many numbers compactly.
- `array(typecode, values)` needs the type code, and the code is enforced.
- The first index is 0, and **the last index is one less than the length**. `marks[-1]` is the last element.
- A slice goes up to but not including its second number.
- `append()` adds one element, `extend()` adds each item of a collection.
- `remove()` works by value and returns nothing; `pop()` works by position and hands the element back.
- `index()` gives a position and raises `ValueError` when the value is missing; `in` just answers yes or no.
- **Methods that change an array return `None`.** Never write `marks = marks.reverse()`.
- `enumerate()` gives the position and the value together — reach for it instead of `range(len(...))`.
- `sum()`, `min()`, `max()`, `round()`, `abs()` and `pow()` are built in and work on any collection.

Next: **String Operations**
