Index and Slice
Count from 0. A slice is a piece of a list or a string. Off-by-one errors break chunks of text and log windows.
An index is a position number. Python starts at 0. The first item is index 0. The second is index 1. The last item of a 4-item list is index 3, which is also -1.
A slice [1:3] keeps b and c. The stop index is not included. The same rule works on text.
Index 0 is the first boxA slice is a piece. You write it as [start:stop]. You get items from start up to — but not including — stop. The same rules work on a string (text) and on a list. Learn them once. Use them on prompts, logs, and chunks.
Count from zero
word[0] is the first character. word[-1] is the last. Negative indexes count from the end: -2 is the second last. word[-len(word)] is the first character again.
A missing index crashes. Python stops with IndexError: that position does not exist. A slice does not crash. A slice that is past the end is just empty. That difference matters when you take memory[-1] on an empty log (crash) versus memory[-5:] on a short log (fine).
word = class="tok-s">"hello"
print(word[0]) class="tok-c"># h
print(word[-1]) class="tok-c"># o
print(word[1:4]) class="tok-c"># ell (indexes 1, 2, 3 — not 4)
nums = [class="tok-s">"a", class="tok-s">"b", class="tok-s">"c", class="tok-s">"d"]
print(nums[1:4]) class="tok-c"># ['b', 'c', 'd']
print(nums[9:12]) class="tok-c"># []If you need the last item but the list might be empty, check first:
if nums:
print(nums[-1])
else:
print(class="tok-s">"no items")Start, stop, and step
You can skip the start or the stop. You can also add a step (how many items to jump).
| Code | Meaning |
|---|---|
s[0] | Item at index 0 |
s[-1] | Last item |
s[1:4] | Indexes 1, 2, 3 (stop is not included) |
s[:3] | From the start up to index 3 |
s[3:] | From index 3 to the end |
s[::2] | Every second item |
s[:] | A copy (a new list with the same items) |
s[-5:] | Last five items (or all, if fewer) |
s[start:stop:step] is the full form. s[::-1] walks backward. That is a trick. Real agent code uses slices to keep a tail of a log, not to reverse a prompt. If you reverse a transcript, the model sees the story backwards.
A step of 2 on "abcde" with [0:5:2] is "ace". Useful in puzzles. Rare in agents. Prefer a loop if you are picking items by a rule other than “a window of the log.”
Copy with [:]
b = a does not copy. Both names point at the same list. If you append through b, you also change a.
c = a[:] makes a new list. Changing c does not change a. list(a) is the same kind of copy. Both are shallow: inner lists inside a are still shared. Deep copy comes later. For a list of strings, [:] is enough.
original = [1, 2, 3]
same = original
copy = original[:]
same.append(99)
print(original) class="tok-c"># [1, 2, 3, 99]
print(copy) class="tok-c"># [1, 2, 3]Strings do not need this copy for safety, because you cannot change a string in place. t = s for two strings is still two names on one text, but nothing can edit that text.
Off-by-one breaks chunking
Chunking means cutting a long text into small pieces (chunks) so a model can read them. Each chunk is a slice. The next chunk should start where the last one stopped.
Off-by-one means you are one item too short or too long. It happens because stop is not included.
If you want 4 letters, the stop is start + 4, not start + 3. The next chunk should start at that same stop. If you start the next chunk one too far, you skip a letter. If you stop one too soon, you drop a letter.
text = class="tok-s">"abcdefgh"
print(text[0:4], text[4:8]) class="tok-c"># abcd efgh — good 4-letter chunks
print(text[0:3], text[3:6]) class="tok-c"># abc def — too short (off by one)A small loop that does this right:
text = class="tok-s">"abcdefgh"
size = 4
start = 0
while start < len(text):
piece = text[start:start + size]
print(piece)
start = start + sizeThe next start is the old stop. That single assignment is the whole trick.
A log window is the same arithmetic. memory[-8:] means start at len(memory)-8, stop at the end. If you write memory[-8:-1] you drop the last item. That is a classic off-by-one: you wanted the last eight, including the newest. The missing last row is often the observation the model needed. Prefer [-n:] with no stop, or [start:].
Indexes on a 5-item list are 0,1,2,3,4. There is no 5. nums[5] crashes. nums[5:] is empty. When you compute i + size as the next start, that number is allowed to equal len. The next slice is then empty and your while loop should stop. start < len(text) is the right test. start <= len(text) would spin on empty slices.
Common mistakes
memory[-1]on an empty list.- Using
s[0:4]and thinking index 4 was included. b = awhen you needed a copy.- Overlapping chunks by restarting at
stop - 1without meaning to. - Reversing a log with
[::-1]and sending that to the model.
Run to execute this in your browser. Nothing is sent to a server.
Change the chunk size in your head from 4 to 2 and predict text[0:2], text[2:4], text[4:6], text[6:8]. Then print those slices. Prediction is how off-by-one dies.
How agents use this
Agents slice the last few messages so the prompt stays short: transcript[-10:]. They also slice long documents into chunks. If the stop index is wrong, a chunk loses a letter or skips one. That is an off-by-one bug. The same slice rules work on strings and on lists.
A window that is too long wastes tokens and buries the newest observation. A window that is too short forgets the user goal. Pick a number, slice, print len(window). When cost spikes, shorten the slice before you blame the model.
Copy before you isolate: if you pass a slice of a log into a helper that appends, a slice of a list is already a new list, so appends stay in the helper. A slice of a string is a new string anyway. Indexing a single dict out of a list of dicts does not copy the dict. Nested data comes in two lessons. For now: slices of lists copy the outer row of labels.
Check your understanding