CodingLad
python

Arrays and Strings in Python — Beginner-Friendly Notes

Arrays and Strings in Python — Beginner-Friendly Notes
0 views
9 min read
#python

Arrays and Strings in Python — Beginner-Friendly Notes

These notes cover the two most basic data structures in Python: lists (used as arrays) and strings. The focus is on how they store data, how we access them, and what each common operation costs in Big-O terms.

1. What Is a Data Structure?

A data structure is a way of organizing and storing data so that a program can use it efficiently.

Suppose we need to store five student marks.

Using separate variables:

mark1 = 75
mark2 = 82
mark3 = 68
mark4 = 90
mark5 = 77

This becomes difficult to manage.

Using a collection:

marks = [75, 82, 68, 90, 77]

Now the values are stored together, and we can process them using indexes and loops.

A data structure organizes related data and defines how we can access, update, add, or remove it.

Common data structures include:

  • Arrays
  • Strings
  • Linked lists
  • Stacks
  • Queues
  • Hash tables
  • Trees
  • Graphs

This post focuses on the two most basic structures: arrays and strings.


2. What Is an Array?

An array stores multiple values in an ordered sequence.

numbers = [10, 20, 30, 40]

Conceptually:

Index:     0     1     2     3
         ┌────┬────┬────┬────┐
Value:   │ 10 │ 20 │ 30 │ 40 │
         └────┴────┴────┴────┘

Each value is called an element, and each element has an index.

Python indexes begin at 0:

print(numbers[0])  # 10
print(numbers[1])  # 20
print(numbers[3])  # 40

The first element is at index 0, not index 1.

Python note

Python's built-in list is not a traditional fixed-size array. It is a dynamic array, meaning its size can grow or shrink.

In beginner-level Python code, lists are commonly used as arrays:

numbers = [10, 20, 30]

3. Array Indexes

Given:

fruits = ["apple", "banana", "mango", "orange"]

The indexes are:

Index:      0          1         2          3
          ┌─────────┬──────────┬─────────┬──────────┐
Value:    │ "apple" │ "banana" │ "mango" │ "orange" │
          └─────────┴──────────┴─────────┴──────────┘

Accessing elements:

print(fruits[0])  # apple
print(fruits[2])  # mango

Negative indexing

Python also supports negative indexes:

Positive:    0          1         2          3
Negative:   -4         -3        -2         -1
           ┌─────────┬──────────┬─────────┬──────────┐
Value:     │ "apple" │ "banana" │ "mango" │ "orange" │
           └─────────┴──────────┴─────────┴──────────┘

Examples:

print(fruits[-1])  # orange
print(fruits[-2])  # mango

-1 means the last element.


4. Reading and Updating Array Elements

Reading an element

numbers = [10, 20, 30]
 
value = numbers[1]
 
print(value)  # 20

Accessing an element by index usually takes:

Time = O(1)

The program can directly locate the requested position.

Updating an element

numbers[1] = 99
 
print(numbers)

Output:

[10, 99, 30]

Updating a known index also usually takes:

Time = O(1)

Direct index access and update are fast because the position is already known.


5. Traversing an Array

Traversing means visiting each element.

numbers = [10, 20, 30, 40]
 
for number in numbers:
    print(number)

Output:

10
20
30
40

If the array contains n elements, the loop visits all n elements:

Time = O(n)

Traversing with indexes

for index in range(len(numbers)):
    print(index, numbers[index])

Output:

0 10
1 20
2 30
3 40

Use direct traversal when only the value is needed:

for number in numbers:
    print(number)

Use enumerate() when both the index and value are needed:

for index, number in enumerate(numbers):
    print(index, number)

6. Searching an Array

Suppose we want to find 30:

numbers = [10, 20, 30, 40]

A linear search checks elements one by one:

def linear_search(arr, target):
    for index, value in enumerate(arr):
        if value == target:
            return index
 
    return -1

Calling:

print(linear_search(numbers, 30))

Output:

2

Execution:

10 == 30? No
20 == 30? No
30 == 30? Yes → return index 2

In the worst case, the target is at the end or does not exist:

Time = O(n)

7. Adding Elements

Append to the end

numbers = [10, 20, 30]
 
numbers.append(40)
 
print(numbers)

Output:

[10, 20, 30, 40]

Appending to a Python list is usually:

O(1) amortized

Occasionally, Python must allocate a larger internal array and copy the existing elements. But averaged across many appends, the cost is considered constant.

Insert at a position

numbers.insert(1, 15)
 
print(numbers)

Output:

[10, 15, 20, 30, 40]

To insert 15 at index 1, later elements must move right:

Before: [10, 20, 30, 40]
             ↓ shift right
After:  [10, 15, 20, 30, 40]

Therefore:

Time = O(n)

8. Removing Elements

Remove the last element

numbers = [10, 20, 30]
 
removed = numbers.pop()
 
print(removed)  # 30
print(numbers)  # [10, 20]

Removing the last element is usually:

Time = O(1)

Remove by index

numbers = [10, 20, 30, 40]
 
numbers.pop(1)
 
print(numbers)

Output:

[10, 30, 40]

Later elements must shift left:

Time = O(n)

Remove by value

numbers.remove(30)

Python first searches for the value and then shifts later elements:

Time = O(n)

9. append() vs extend()

append() adds its argument as one element:

numbers = [1, 2]
 
numbers.append([3, 4])
 
print(numbers)

Output:

[1, 2, [3, 4]]

The nested list [3, 4] becomes one new element.

extend() adds each element separately:

numbers = [1, 2]
 
numbers.extend([3, 4])
 
print(numbers)

Output:

[1, 2, 3, 4]

The difference:

append([3, 4]) → add the entire list as one element
extend([3, 4]) → add 3 and 4 separately

10. Array Slicing

Slicing extracts part of an array:

numbers = [10, 20, 30, 40, 50]
 
print(numbers[1:4])

Output:

[20, 30, 40]

The general syntax is:

array[start:stop:step]

The start index is included, but the stop index is excluded:

numbers[1:4]
         ↑ ↑
       include 1
       exclude 4

More examples:

numbers[:3]    # [10, 20, 30]
numbers[2:]    # [30, 40, 50]
numbers[::2]   # [10, 30, 50]
numbers[::-1]  # [50, 40, 30, 20, 10]

A slice creates a new list containing the selected elements.

If the slice contains k elements:

Time  = O(k)
Space = O(k)

11. What Is a String?

A string is an ordered sequence of characters.

text = "hello"

Conceptually:

Index:     0     1     2     3     4
         ┌────┬────┬────┬────┬────┐
Value:   │ h  │ e  │ l  │ l  │ o  │
         └────┴────┴────┴────┴────┘

Characters can be accessed using indexes:

print(text[0])   # h
print(text[1])   # e
print(text[-1])  # o

Accessing a character by index takes:

Time = O(1)

12. Strings Are Immutable

Python lists are mutable. Their elements can be changed:

numbers = [10, 20, 30]
numbers[1] = 99

Strings are immutable. Their characters cannot be changed directly:

text = "hello"
 
text[0] = "H"

This produces an error:

TypeError: 'str' object does not support item assignment

To create "Hello", we must make a new string:

text = "H" + text[1:]
 
print(text)

Output:

Hello

Mutable means an object can be changed after creation. Immutable means changing it requires creating a new object.


13. Traversing a String

A string can be traversed like an array:

text = "hello"
 
for character in text:
    print(character)

Output:

h
e
l
l
o

For a string containing n characters:

Time = O(n)

With indexes:

for index, character in enumerate(text):
    print(index, character)

14. String Slicing

String slicing follows the same rules as list slicing:

text = "python"
 
print(text[0:3])  # pyt
print(text[2:])   # thon
print(text[:4])   # pyth
print(text[::-1]) # nohtyp

Because strings are immutable, slicing creates a new string.

For a slice containing k characters:

Time  = O(k)
Space = O(k)

15. Comparing Strings

Strings can be compared using:

word1 = "hello"
word2 = "hello"
 
print(word1 == word2)  # True

Python compares characters from left to right.

print("apple" == "apply")  # False

The first four characters match:

a == a
p == p
p == p
l == l

The final characters differ:

e != y

In the worst case, Python may compare all characters:

Time = O(n)

16. Common String Operations

text = "hello world"

Length

len(text)  # 11

Convert to uppercase

text.upper()  # "HELLO WORLD"

Convert to lowercase

text.lower()  # "hello world"

Check prefix or suffix

text.startswith("hello")  # True
text.endswith("world")    # True

Find a substring

text.find("world")  # 6

It returns -1 when the substring is not found.

Replace text

text.replace("world", "Python")

Result:

"hello Python"

These methods return new strings because the original string cannot be modified.


17. String Concatenation

Concatenation means joining strings:

first = "Hello"
second = "World"
 
result = first + " " + second
 
print(result)

Output:

Hello World

Because strings are immutable, concatenation creates a new string.

Repeated concatenation inside a loop can be inefficient:

result = ""
 
for word in words:
    result += word

As the string grows, Python may repeatedly copy its contents.

A better approach for many strings is:

result = "".join(words)

Example:

words = ["Python", "is", "fun"]
 
sentence = " ".join(words)
 
print(sentence)

Output:

Python is fun

EXAMPLE 1 — Find the Largest Element

def find_largest(arr):
    largest = arr[0]
 
    for value in arr[1:]:
        if value > largest:
            largest = value
 
    return largest

Calling:

find_largest([4, 9, 2, 7])

Execution:

largest = 4
compare 9 with 4 → largest = 9
compare 2 with 9 → unchanged
compare 7 with 9 → unchanged

Result:

9

Complexity:

Time  = O(n)
Space = O(1)

Python note: arr[1:] creates a copy, which technically uses extra space. To keep auxiliary space at O(1), use indexes:

def find_largest(arr):
    largest = arr[0]
 
    for index in range(1, len(arr)):
        if arr[index] > largest:
            largest = arr[index]
 
    return largest

EXAMPLE 2 — Reverse an Array

Using slicing

numbers = [1, 2, 3, 4]
 
reversed_numbers = numbers[::-1]

Result:

[4, 3, 2, 1]

Complexity:

Time  = O(n)
Space = O(n)

Slicing creates a new array.

Reversing in place

def reverse_array(arr):
    left = 0
    right = len(arr) - 1
 
    while left < right:
        arr[left], arr[right] = arr[right], arr[left]
 
        left += 1
        right -= 1

Execution:

[1, 2, 3, 4]
 ↑        ↑
swap 1 and 4

[4, 2, 3, 1]
    ↑  ↑
swap 2 and 3

[4, 3, 2, 1]

Complexity:

Time  = O(n)
Space = O(1)

This approach uses the two-pointer technique.


EXAMPLE 3 — Check Whether a String Is a Palindrome

A palindrome reads the same forward and backward:

"madam"
"racecar"
"level"

A simple solution:

def is_palindrome(text):
    return text == text[::-1]

A two-pointer solution:

def is_palindrome(text):
    left = 0
    right = len(text) - 1
 
    while left < right:
        if text[left] != text[right]:
            return False
 
        left += 1
        right -= 1
 
    return True

For "level":

l e v e l
↑       ↑    l == l

  ↑   ↑      e == e

    ↑        pointers meet

Result:

True

Complexity:

Time  = O(n)
Space = O(1)

EXAMPLE 4 — Count Character Frequencies

def count_characters(text):
    frequency = {}
 
    for character in text:
        frequency[character] = frequency.get(character, 0) + 1
 
    return frequency

Calling:

count_characters("banana")

Result:

{
    "b": 1,
    "a": 3,
    "n": 2
}

The dictionary stores each character and its count.

Complexity:

Time  = O(n)
Space = O(k)

Here, k is the number of distinct characters.


Array vs String

Array/listString
Ordered collection of elementsOrdered collection of characters
Can contain different data typesContains characters
MutableImmutable
Elements can be updated directlyCharacters cannot be updated directly
Example: [10, 20, 30]Example: "hello"

Common Mistakes

Mistake 1 — Using the wrong index

numbers = [10, 20, 30]
 
print(numbers[3])

Valid indexes are:

0, 1, 2

Therefore, numbers[3] produces:

IndexError: list index out of range

The last valid index is:

len(numbers) - 1

Mistake 2 — Changing a string directly

Incorrect:

text = "hello"
text[0] = "H"

Correct:

text = "H" + text[1:]

Mistake 3 — Confusing append() and extend()

numbers.append([3, 4])

Result:

[1, 2, [3, 4]]

But:

numbers.extend([3, 4])

Result:

[1, 2, 3, 4]

Mistake 4 — Modifying a list while traversing it

This can skip elements:

numbers = [1, 2, 2, 3]
 
for number in numbers:
    if number == 2:
        numbers.remove(number)

A safer approach is to create a new list:

numbers = [
    number
    for number in numbers
    if number != 2
]

Complexity Summary

OperationPython listString
Access by indexO(1)O(1)
Update by indexO(1)Not allowed
TraverseO(n)O(n)
SearchO(n)O(n)
Append to endO(1) amortizedNot applicable
Insert/remove at beginningO(n)Not applicable
Remove last elementO(1)Not applicable
Slice k elementsO(k)O(k)
Reverse using slicingO(n)O(n)
ConcatenationDepends on added elementsCreates a new string

Quick Rules

  1. Python lists behave like dynamic arrays.

  2. Array and string indexes start at 0.

  3. Negative indexes count from the end:

-1 → last element
-2 → second-last element
  1. Accessing a known index is usually:
O(1)
  1. Traversing or linearly searching n elements is:
O(n)
  1. Inserting or removing near the beginning of an array requires shifting elements:
O(n)
  1. Lists are mutable, but strings are immutable.

  2. Slicing creates a new list or string.

  3. append() adds one object; extend() adds each element from an iterable.

  4. Two pointers are useful for reversing arrays and checking palindromes.


Final Summary

Arrays and strings are ordered data structures that use indexes to access their contents.

Python uses lists as dynamic arrays:

numbers = [10, 20, 30]

A string is an immutable sequence of characters:

text = "hello"

The most important differences are:

List   → mutable
String → immutable

Both structures support indexing, traversal, searching, and slicing. Understanding their operation costs is important:

Direct access       → O(1)
Full traversal      → O(n)
Linear search       → O(n)
Insert at beginning → O(n)
Slice k elements    → O(k)

Arrays and strings also introduce important problem-solving techniques such as linear traversal, frequency counting, and two pointers. These techniques appear repeatedly in coding interviews and more advanced data structures.