# Python Strings Are Immutable, and It Changed How I Think About Slicing

Strings showed up back in the data types post as one of nine basic types, but it turns out that was barely scratching the surface. There's a whole world of indexing, slicing, and a genuinely important rule about what you can and can't do to a string once it exists.

> **Term check: string** A string is a sequence of characters. Python specifically stores these as Unicode characters (as opposed to the more limited ASCII character set some other languages default to), which is part of why Python handles things like emoji and non-English text so naturally.

## Creating a string, the right way

Single or double quotes both work, but there's a real reason to lean toward double quotes as your default: a string with an apostrophe in it, like `it's my car`, will break if you wrap it in single quotes, since Python reads that apostrophe as the string ending early.

```python
s = "it's my car"  # works fine
# s = 'it's my car'  # breaks, Python thinks the string ends after "it"
```

For anything spanning multiple lines, triple quotes are required, the same rule from the literals post:

```python
info = """
My name is Darshan.
I am learning Python.
"""
```

And if you need to convert some other type into a string, `str()` handles that directly:

```python
a = 23
print(str(a))  # "23", now a string instead of an int
```

## Accessing characters by index

Every character in a string has a position, starting at `0`.

```python
s = "welcome to python"
print(s[0])   # w
print(s[1])   # e
```

Try to access a position beyond the string's actual length, and Python throws an `IndexError: string index out of range`. `len(s)` tells you exactly how long a string is, which is a good habit before indexing into unfamiliar strings.

Python also supports negative indexing, counting from the end instead of the beginning.

![](https://cdn.hashnode.com/uploads/covers/65190f55c8ba679c8f0bec8b/40cf7e35-f8dc-49f8-b01e-62f30799b0a3.png align="center")

```python
print(s[-1])  # n, the last character
print(s[-2])  # o, second to last
```

Neither direction is more "correct," positive indexing is often more intuitive, negative indexing is genuinely convenient when you care about something near the end of a string without wanting to calculate its exact length first.

## Slicing: pulling out more than one character

Slicing uses a `start:stop` format inside the brackets, and follows the same rule as `range()`: the stop position is excluded.

```python
s = "welcome to python"
print(s[0:7])   # welcome
```

A few shortcuts worth knowing: leaving out the start defaults to `0`, leaving out the stop defaults to the end of the string, and leaving out both just returns the whole string.

```python
print(s[:7])    # welcome (same as s[0:7])
print(s[1:])    # elcome to python (everything from index 1 onward)
print(s[:])     # the entire string
```

You can add a third value for a step, skipping characters:

```python
print(s[0:7:2])  # wloe (every second character within that slice)
```

## Reversing a string with slicing

This is the trick that made slicing click for me. A negative step walks backward instead of forward, and reverses the string entirely when you leave the start and stop empty.

```python
print(s[::-1])  # nohtyp ot emoclew
```

One rule to keep in mind whenever the step is negative: the logical "start" of your slice needs to be the higher index, and "stop" the lower one, since you're now moving right to left instead of left to right.

## Strings are immutable

> **Term check: immutable** Immutable means unchangeable. Once a string is created, you cannot alter its contents, not by adding a character, not by editing one, not by deleting one.

```python
s = "welcome to python"
s[0] = "a"
# TypeError: 'str' object does not support item assignment
```

Trying to delete a single character fails the same way:

```python
del s[0]
# TypeError: 'str' object doesn't support item deletion
```

You genuinely cannot modify a string's actual contents in place, ever. What you *can* do is delete the variable entirely, which removes the reference, not the string's internal contents:

```python
del s
print(s)
# NameError: name 's' is not defined
```

This distinction, deleting a variable versus modifying a string, matters a lot once list, tuple, set, and dictionary show up later in this series, since some of those types behave very differently on exactly this point.

## Common mistakes I'd flag here

*   Forgetting slicing's stop index is excluded, the same off-by-one trap from `range()` showing up again here.
    
*   Trying to reverse a slice with a negative step but keeping start smaller than stop, which returns an empty result instead of the reversed string.
    
*   Attempting to modify a string in place (`s[0] = "x"`), forgetting strings are immutable, and getting genuinely confused by the `TypeError`.
    
*   Confusing deleting a variable (`del s`) with deleting part of a string's contents, which isn't possible at all.
    

## Quick recap

*   Strings are sequences of Unicode characters, and double quotes are generally the safer default over single quotes.
    
*   Every character has a positive index (from the start) and a negative index (from the end).
    
*   Slicing (`s[start:stop:step]`) extracts a substring, with the stop index excluded, and a negative step reverses direction.
    
*   `s[::-1]` is the compact idiom for reversing an entire string.
    
*   Strings are immutable: no in-place edits, additions, or deletions are possible, only replacing the whole variable or deleting it entirely.
    

## What's next

Next up: string operations, arithmetic, relational, logical, and membership operators, all applied specifically to strings.
