Skip to content
  • Home
  • How To
  • M0
  • Module 1 - Python
  • Agents
  • M2
  • M3
  • M4
  • M5
  • M6
  • Progress
  • Contact
  • Pricing
  • Sign in
  • Future
  1. Module 1M1
  2. ›
    S2Control Flow
  3. ›
    Lesson 3 of 4The for Loop, Iterables, and range
  4. ›
    S2›Lesson 3 of 4
  1. Module 1
  2. ›
  3. Section 2
  4. ›
  5. Lesson 3

The for Loop, Iterables, and range

FREE·~1.5 h·2 labs·4 questions·1 assignment


Context

A for loop walks through the items of a collection one at a time, handing each to the same block of code. It stops when the items run out, so the number of passes comes from the data rather than from a condition you maintain.

This is the shape of nearly all data processing. Every row of a file, every value in a column, every file in a directory of results, each one visited in turn and handled the same way. Where a running total is needed the loop carries it, and where a filtered subset is needed the loop builds it by keeping some items and passing over others. A fixed count of repetitions, such as five training rounds, comes from asking for a range of numbers to walk.

Choosing the wrong loop costs correctness, not just elegance. Rewriting a walk over rows as a manual counter reintroduces the never-ending loop and the off-by-one that skips the first record or reads past the last, and a walk that quietly visits characters instead of rows produces a count in the thousands that looks like real data. The previous lesson repeated on a condition, this lesson repeats over contents.


Goal

  • Iterate over a sequence with a for loop, binding each item to a name in turn.
  • Generate numeric sequences with range using start, stop, and step.
  • Predict exactly which integers range produces, avoiding the stop-exclusive off-by-one trap.
  • Loop over a string character by character, recognizing it as an iterable.
  • Explain why for is preferred over while when the number of items is known.

Prerequisites

Neither is required and the lesson does not assume you opened them. They are here only if you would rather see the shape before the detail, and Theory builds it from nothing regardless.

  • YouTube - "Python Full Course for Beginners" by Programming with Mosh. The segment on for and range. Watch for one picture, a single pass per item.
  • Official docs - The Python Tutorial, "The range() Function". The canonical introduction to start, stop and step, with the exclusive stop shown in examples.

Table of Contents

  • for Loop
  • Iteration Variable
  • Iterable
  • range

Theory

for Loop

What. A for loop repeats its block once for each item in a collection, automatically taking the next item every pass and stopping when the items run out. Why. When you already know what you are looping over, the characters of a string, a fixed range of numbers, soon the rows of a dataset, for removes all the bookkeeping a while needs. There is no counter to initialize, no condition to write, no update to remember, so there is no way to forget the update and hang the program. When. Use for whenever the number of passes is determined by a collection or a known count. Reserve while for the open-ended "loop until a condition flips" cases from the last lesson.

Nothing needs installing, for is a keyword built into the language. The shape is for NAME in COLLECTION: followed by an indented block. Each pass, NAME is bound to the next item and the block runs.

# 'for' walks the collection, binding each item to 'char' in turn.
for char in "loss":
    print(char)

This prints l, o, s, s, one line per character. The loop is guaranteed to terminate, because the collection is finite and the loop stops when it is exhausted. That guarantee is structural rather than something you had to arrange.

The contrast with while is the whole point. The same character-printing task with a while requires manual indexing (text[i] is the character at position i, counting from 0) and a termination check that you can get wrong. for folds all of that into the language.

# The SAME task with 'while' needs an index, a condition, and an update.
text = "loss"
i = 0
while i < len(text):  # len() from Lesson 1.1.2, manual condition
    print(text[i])    # manual indexing into the string
    i += 1            # manual update you could forget (-> infinite loop)

Identical output, and three new things that can go wrong: the index, the condition and the update. The for version has none of the three, which is exactly why it is preferred whenever you are stepping through a known collection. Fewer moving parts, fewer ways to be wrong.

DON'T:

  • DON'T use a while with a manual counter when a for fits, the for is shorter and cannot forget its update.
  • DON'T forget the colon : after the collection, like every block header, the for header ends in a colon.

Iteration Variable

What. The iteration variable is the name in for NAME in ... that is bound to the current item on each pass. It takes a new value automatically every time the loop advances. Why. It is how the loop body reaches the current item. Naming it well, char, epoch, row, makes the loop read like a description of the work. When. You choose and name it every time you write a for. Pick a singular, descriptive name for one element of whatever you are iterating.

The iteration variable is rebound each pass (the rebinding from Lesson 1.1.3, now automatic). After the loop ends, it keeps the last value it was bound to, it does not disappear, though relying on its post-loop value is usually a sign of fragile code.

# 'epoch' is the iteration variable, it is rebound to each number in turn.
for epoch in range(3):          # range(3) yields 0, 1, 2 (covered below)
    print("epoch", epoch)
# After the loop, 'epoch' still holds its LAST value.
print("last epoch was", epoch)  # last epoch was 2

The name is a fresh binding to the current item on every pass, which is why the body can simply use it with no indexing at all. It also survives the loop, still holding whatever it was bound to last, which is occasionally useful and more often a trap.

If you do not actually need the item, you just want to repeat something N times, the convention is to name the iteration variable a single underscore _, signaling "I am intentionally ignoring this."

# '_' is the conventional name for an iteration variable you do not use.
for _ in range(3):
    print("retrying connection")  # printed 3 times, item ignored

In a script the underscore is an ordinary variable name, and a for loop binds it like any other. The whole convention is aimed at human readers, and it says "the value here is deliberately unused", which is what you want when you care how many times the body runs rather than which item it is on.

DON'T:

  • DON'T rely on the iteration variable's value after the loop for logic, it holds only the last value it was bound to, which is rarely what you mean.
  • DON'T give it a plural or vague name like items or x, name it for one element, like item or score.

Iterable

What. An iterable is anything that can be stepped through one item at a time, a string, a range, and soon lists and other collections. A for loop works on any iterable. Why. It is the unifying abstraction behind every for loop: you do not learn a separate loop for strings and another for numbers, you learn one for that works on everything iterable. This is one of the ideas that makes Python feel consistent. When. The concept matters whenever you ask "can I loop over this?", if a value is iterable, for ... in value: works, full stop.

The point of the abstraction is that the same for loop handles different kinds of collections without changing its shape. A string yields its characters, a range yields its numbers, the loop does not care which, it just asks for the next item until there are none.

# A string is iterable: looping yields its characters.
for symbol in "AML":
    print(symbol)  # A, M, L
# A range is iterable: looping yields its numbers.
for number in range(3):
    print(number)  # 0, 1, 2

Both loops have the identical structure and only the iterable differs. The string being iterable is not a special case bolted on for strings, it follows from what a string already was in Lesson 1.1.2, an ordered sequence of characters. This is why "is it iterable?" is the only question that decides whether a for loop applies.

The most important iterable you will meet soon is the list (an ordered collection of values, covered fully in a later section on collections), and a data file read line by line is an iterable of rows, though a pandas table, met in Lesson 2.4.1, hands a bare for its column names instead. Everything you learn about looping over a string or a range transfers directly, because the for loop treats them all the same way.

# A list is iterable too (full coverage in a later section), shown here as a preview.
for score in [0.91, 0.88, 0.95]:  # a list, an ordered collection of values
    print("score:", score)

The same for shape works on a collection of numbers exactly as it did on a string's characters, with nothing changed but the thing after in.

DON'T:

  • DON'T try to for-loop over a non-iterable like a plain number, for x in 5: raises a TypeError, because a single integer is not a sequence of items.
  • DON'T assume looping needs indices, iterating yields the items themselves, no index required.

range

What. range generates a sequence of evenly spaced integers for counted loops. It takes up to three arguments: range(stop), range(start, stop), or range(start, stop, step). Why. It is the standard way to repeat something a known number of times, or to walk a sequence of integers, without building the numbers by hand. It is the iterable you will pair with for most often. When. Use range whenever you need a counted loop, "do this N times", "count from 2 to 10", "step by 5", instead of a manual counter.

The single most important rule about range is that the stop value is exclusive: range(5) yields 0, 1, 2, 3, 4, five numbers starting at 0, stopping before 5. This stop-exclusive behavior is the classic off-by-one trap, so internalize it now.

# One argument: range(stop) starts at 0 and stops BEFORE 'stop'.
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4  (NOT 5)
# Two arguments: range(start, stop) starts at 'start', stops before 'stop'.
for i in range(2, 6):
    print(i)  # 2, 3, 4, 5  (NOT 6)

The useful consequence, and the thing to remember instead of the individual cases, is that with the default step of 1 the count of numbers is exactly stop - start, whenever stop is at least start. range(5) gives five values and range(2, 6) gives four. If you want 6 included you must write range(2, 7), because the stop is never reached.

The third argument, step, sets the gap between numbers and can be negative to count downward.

# Three arguments: range(start, stop, step) jumps by 'step' each time.
for i in range(0, 10, 2):
    print(i)  # 0, 2, 4, 6, 8  (step of 2, stop before 10)
# A negative step counts DOWN, stop is still exclusive.
for i in range(5, 0, -1):
    print(i)  # 5, 4, 3, 2, 1  (stops before 0)

The stop-exclusive rule holds in both directions, so counting down stops before 0 just as counting up stopped before 10, and neither ever lands on its stop value.

One more detail that is easy to miss and matters later: range does not build its numbers in memory at once, it produces them one at a time as the loop asks for them. That is why range(1000000) is cheap to make: it holds its start, stop and step, never the million numbers.

DON'T:

  • DON'T expect range(5) to include 5, the stop value is exclusive, so it ends at 4.
  • DON'T write range(1, 10) when you mean to include 10, use range(1, 11), since stop is never reached.

Lab Exercises

Both problems first, then both solutions. Do not peek.

Lab Exercise 1: Loop a String, and Ignore the Item

Covers the first half (for Loop, Iteration Variable).

In a script spell.py, use a for loop to print each character of the string "epoch" on its own line. Then add a second loop that prints "hello" once for each character of that same string, using _ as the iteration variable because you do not need the character itself. Confirm the first loop prints five letters and the second prints "hello" five times.

Finally, after the first loop, print the iteration variable one more time and say in a comment what it holds and why.

Lab Exercise 2: Counted Loops, and What They Add Up To

Covers the second half (Iterable, range).

Create counted.py. Use for i in range(...) to print the numbers 0 through 4, then a second loop printing 1 through 10 inclusive, then a third printing the even numbers 0, 2, 4, 6, 8. Beside each, state in a comment the exact range(...) arguments you used and why, paying attention to the exclusive stop.

Then, in the same file, use a single for loop over a range to accumulate the sum of the integers 1 through 100 inclusive into total, and to count how many of them are divisible by 7 into n_div7. Initialize both accumulators before the loop and print both after. Confirm total is 5050. In a final comment, explain why this for version cannot accidentally become an infinite loop the way the while of the last lesson could.


Solution 1: Loop a String, and Ignore the Item
# 'for' walks the string, binding each character to 'char' in turn.
for char in "epoch":
    print(char)     # e, p, o, c, h

# '_' is the conventional name for an iteration variable you do not use.
for _ in "epoch":
    print("hello")  # printed 5 times, one per character

# The iteration variable survives the loop, holding its LAST value.
print(char)         # h

Run it:

# Print each character, then repeat a message once per character.
python3 spell.py

The first loop prints the five letters, the second prints hello five times. The second loop still runs once per character, it simply never looks at the character, and _ is how that intent is signaled to a reader.

The final print(char) shows h, the last character the name was bound to. The loop did not clean up after itself, and the name is still there with its final value.

DON'T:

  • DON'T use a counter and manual indexing to walk a string, for char in text: hands you the character directly.
  • DON'T build logic on the iteration variable's value after the loop, it holds only the last value it was bound to, which is rarely what you meant.
Solution 2: Counted Loops, and What They Add Up To
# range(stop): starts at 0, stops BEFORE 5, so five values.
for i in range(5):
    print(i)                               # 0 1 2 3 4

# range(start, stop): 11 as the stop because the stop is never reached.
for i in range(1, 11):
    print(i)                               # 1 through 10 inclusive

# range(start, stop, step): step of 2, still stopping before 10.
for i in range(0, 10, 2):
    print(i)                               # 0 2 4 6 8

# INITIALIZE both accumulators before the loop.
total = 0
n_div7 = 0
for k in range(1, 101):                    # 101 as the stop so that 100 is included
    total += k
    if k % 7 == 0:                         # divisibility test with '%' from Lesson 1.1.4
        n_div7 += 1
print("total:", total, "n_div7:", n_div7)  # total: 5050 n_div7: 14
# This cannot loop forever. A 'while' repeats until a condition YOU maintain turns
# false, so forgetting the update hangs it. A 'for' repeats until the iterable is
# exhausted, and range(1, 101) has exactly 100 values whatever the body does.

Run it:

# Three counted loops, then a sum and a filtered count over 1 to 100.
python3 counted.py

Each of the three ranges stops before its stop value, which is why range(1, 11) is what "1 through 10" is spelled as. The accumulator loop prints total: 5050 n_div7: 14, the fourteen multiples of seven being 7 through 98.

The comment about infinite loops is the part worth carrying forward. This for cannot hang because range(1, 101) fixes the number of passes before the body ever runs, and nothing the body does can extend it. That removes an entire class of bug rather than making it less likely.

DON'T:

  • DON'T write range(1, 10) when you mean to include 10, the stop is never reached, so it is range(1, 11).
  • DON'T reach for a while with a manual counter when the number of passes is already known, the for cannot forget its update.

Theory Assessment

Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.

Q1: One Pass Per Item, and the Name You Do Not Use

Two parts on how a for steps. First, explain what a for loop does on each pass and what makes it stop, without referring to any condition you write. Second, explain what for _ in range(3): means, what the underscore does differently from any other name, and when you would choose it.

Hint: A for is driven by the collection rather than by a test. For the second part, ask whether Python treats _ specially or whether the convention is aimed at somebody else.

Q2: Iterable or Not, and Why for Beats while Here

Two parts. First, for x in "AML": works and for x in 5: raises a TypeError, explain what distinguishes the two and what "iterable" actually means. Second, give the main reason to prefer a for over a while when walking a known collection, in terms of what can go wrong rather than in terms of line count.

Hint: One of those two values is a sequence of items and the other is a single value. For the second part, count the things you have to write correctly in each version, and ask what happens when you get each one wrong.

Q3: What range Produces, and the Off-by-One

Two parts on range. First, state exactly what range(5), range(2, 6) and range(0, 10, 2) produce, and give the rule for how many numbers a range contains. Second, a student writes range(1, 10) intending "the numbers 1 through 10", say what they actually get, and what the fix is.

Hint: The stop value is never reached, in either direction. For the count, there is a one-line formula in terms of start and stop.

Q4: The Variable That Was Not There

The lesson said the iteration variable survives the loop, still holding the last value it was bound to. So this prints 2:

for i in range(3):
    pass
print(i)

Now consider this, which is the same code with one number changed:

for i in range(0):
    pass
print(i)

Answer three things. First, say what the second snippet does and why. Second, explain why this does not contradict the rule the lesson stated, by saying precisely what that rule depends on. Third, connect it to the zero-iteration while from Lesson 1.2.2: both loops ran zero passes, but only one of them leaves you with a broken print afterwards. Explain the difference, and say which of the two outcomes you would rather have.

Hint: Ask what actually binds the iteration variable, and how many times that binding happened here. For the third part, the while lesson's remaining was initialized before its loop, and the iteration variable is not.


Answer 1:

On each pass a for loop takes the next item from its iterable, binds the iteration variable to it, and runs the body once. It stops when the iterable is exhausted, so the number of passes is decided by the collection rather than by any test you write. That is the structural difference from a while: there is no condition to design and no update to remember, and the loop cannot run longer than the collection is.

for _ in range(3): runs its body three times while deliberately ignoring the value. In a script _ is an ordinary variable name, and the loop binds it each pass just the same. The convention is aimed entirely at human readers, and it says the value is unused on purpose rather than by oversight, which is what you want when you care how many times something happens rather than what it happens to.

DON'T give an iteration variable a plural or vague name like items or x, name it for one element, since that is what it holds.

Answer 2:

A string is iterable, meaning it can hand over its contents one item at a time, and for a string those items are its characters. The integer 5 is a single value with no contents to hand over, so for x in 5: raises a TypeError. Being iterable is the only property a for loop requires, which is why one loop shape works on strings, ranges, lists and eventually rows of a dataset without changing.

The reason to prefer for is the number of things that can be wrong. The while version of the same walk requires an index, a condition and an update, and each has its own failure: a wrong starting index skips or repeats an item, a wrong comparison runs off the end or stops early, and a missing update hangs the program outright. The for version has none of the three, so those bugs are not made less likely, they are made impossible to write.

DON'T assume iterating requires indices, a for hands you the items themselves.

Answer 3:

range(5) produces 0, 1, 2, 3, 4. range(2, 6) produces 2, 3, 4, 5. range(0, 10, 2) produces 0, 2, 4, 6, 8. In all three the stop value is never reached, and with a step of one the count of numbers is simply stop - start, whenever stop is at least start.

A student writing range(1, 10) for "the numbers 1 through 10" actually gets 1 through 9, nine numbers rather than ten, because 10 is the stop and the stop is excluded. The fix is range(1, 11). This is the classic off-by-one, and the reliable habit is to write the stop as one past the last value you want.

DON'T expect range(5) to include 5, and don't work around the exclusive stop with a - 1 somewhere else, write the stop you actually mean.

Answer 4:

The second snippet raises NameError: name 'i' is not defined. Did you mean: 'id'?. range(0) is empty, so the loop body never runs, and more to the point the loop never gets as far as binding i to anything. The print then refers to a name that was never created.

This does not contradict the rule, because the rule depends on something the first snippet has and the second does not: at least one pass. The iteration variable is bound by the act of taking an item, so "it keeps its last value" silently assumes there was a last value. With zero items there is no last value and therefore no binding, and the name never comes into existence at all. The rule is really "if the loop ran at least once, the variable survives holding the last value it was bound to".

The contrast with Lesson 1.2.2 is the useful part. Both loops ran zero passes, but that lesson's remaining was initialized before its loop, so after zero passes it still existed and still held 0. The iteration variable is initialized by the loop, so after zero passes it does not exist. So the while version leaves remaining, and any total set up the same way, quietly holding its starting value, and this one fails loudly with a NameError on the very next line. The loud one is far better. A name that does not exist stops the program at the exact line that was wrong, while a zero that means "no data" is indistinguishable from a zero that means "the answer is zero", and can travel a long way before anyone notices.

DON'T use an iteration variable after its loop without knowing the loop ran, and DON'T read the NameError as the worse outcome here, it is the one that told you.


Assignment

One assignment. The problem and its hints first, then the solution.

Assignment: A Multiplication Grid

Create grid_row.py that builds and prints rows of a multiplication table using range.

For each row in range(1, 4), accumulate a single string line that concatenates row * col for each col in range(1, 6), separated by spaces. Use str() from Lesson 1.1.4 to convert each product, and reset line to "" at the start of every row. Print each completed line. Then use enumerate(range(1, 4)) to print a zero-based row index beside each line as well.

In comments, settle three things: why line is reset inside the outer loop rather than before it, how enumerate supplies the index without a manual counter, and what the printed index would be if you used the row value itself instead, along with why the two differ.

Hints:

  • For each outer row, start line = "", then in an inner loop build it up with line += str(row * col) + " " before printing. Resetting per row is what keeps the rows separate, and it is the accumulator rule from the last lesson applied to the inner loop, not an exception to it.
  • enumerate(range(1, 4)) yields the pairs (0, 1), (1, 2) and (2, 3), so the loop header takes two names at once, an index and the item.

Solution:
# 'enumerate' yields (index, item) pairs, so the header binds two names per pass.
for idx, row in enumerate(range(1, 4)):
    # RESET the line accumulator at the start of every row.
    line = ""
    for col in range(1, 6):           # inner loop: 5 columns per row
        line += str(row * col) + " "  # str() so '+' joins text, not numbers
    print(idx, "|", line.rstrip())    # rstrip() drops the trailing space
# 'line' is reset INSIDE the outer loop because each row is its own accumulation.
# That is the same rule as before, not an exception to it: an accumulator is
# initialized immediately before the loop that fills it, and here the loop that
# fills 'line' is the INNER one, so the reset belongs in the outer body.
# 'enumerate' supplies the index, so there is no counter to initialize or update
# and no way to forget the increment.
# Using 'row' as the index instead would print 1, 2, 3 rather than 0, 1, 2, since
# 'row' is the VALUE being iterated and 'idx' is its POSITION. They differ here
# because the range starts at 1, and they would coincide only if it started at 0.

Run it:

# Print three rows of the multiplication table with a zero-based index.
python3 grid_row.py

The script prints three lines: 0 | 1 2 3 4 5, then 1 | 2 4 6 8 10, then 2 | 3 6 9 12 15.

The reset question is the one worth being precise about, because it looks like it contradicts the previous lesson's rule that an accumulator is initialized before the loop and never inside it. It does not. There are two loops here, and line accumulates across the inner one, so the position that satisfies the rule, immediately before the loop that fills it, is inside the outer body. Move it above the outer loop and the rows run together into one line, which is the original rule broken from the other side: the accumulator now spans more than the loop that fills it.

The index question is smaller but sharp: idx and row differ by one throughout, because one is a position and the other is a value, and they would agree only for a range starting at 0. Reaching for whichever is closest to hand is how off-by-one errors get written.

DON'T hoist line = "" above the outer loop, every row after the first would then append to the previous one. And DON'T use the iterated value where you meant its position, they are different numbers whenever the range does not start at zero.


Additional Resources

Optional, and nothing later depends on either one. Worth opening only if this lesson left you curious.

  • YouTube - "Python Tutorial for Beginners" by freeCodeCamp.org. The segment on for loops and range, for a second visual pass over iteration.
  • Official docs - Built-in Functions, the enumerate and zip entries. The clean tools for indexed and parallel iteration, one of which the assignment used and neither of which this lesson had room to teach.

Summary

This lesson refined repetition from a general mechanism into a precise tool. The while loop from last lesson can express any repeated work, but it makes you supply the counter, the condition, and the update by hand, and forgetting any of them is a bug. The for loop trades that generality for safety and clarity: when you already know what you are looping over, it takes each item for you, runs the body once per item, and stops on its own when the items run out. There is no update to forget, so a for over a string or a range can never loop forever on its own. Whenever the number of passes is fixed by a collection or a count, for is the right choice, and that is most of the time.

Underneath the for loop sits the idea of the iterable, the realization that strings, ranges, and soon lists are all just "things you can step through", and that one loop handles them all without changing shape. You saw a string reveal itself as a sequence of characters and a range hand you exactly the integers you asked for, with the stop value always excluded, the off-by-one rule that will catch you at least once no matter how many times you read it here. The accumulator pattern carried straight over from the last lesson, now running cleanly over a for with no risk of running away.

The reason this matters more than any single example is that a dataset is, at its heart, a thing you iterate over. Loading a file, cleaning each row, computing a statistic over a column, transforming every value, nearly every data-processing step you will ever write has the shape of a for loop walking an iterable. You have now met that shape in its simplest forms, over characters and counts, and the move to looping over real collections of data is a change of content, not of structure. The next lesson hands you finer control over loops, leaving early, skipping a pass, and nesting one loop inside another, which is the last piece before loops can express genuinely complex work.


Cheat Sheet

Part A - Reference Table

Concept Syntax Produces / behavior When to avoid
for loop for item in collection: One pass per item Open-ended "until condition" (use while)
Iteration variable the item name Rebound to each item each pass Relying on its value after the loop
Iterable string, range, list, ... Anything steppable by for Looping a single number
range(stop) range(5) 0,1,2,3,4 (stop excluded) Expecting stop to appear
range(start, stop) range(2, 6) 2,3,4,5 Off-by-one on the stop
range(start, stop, step) range(0,10,2) 0,2,4,6,8 Mixing up the step position

Part B - Compact Code Block

for char in "loss":        # strings are iterable -> characters
    print(char)

for i in range(5):         # 0,1,2,3,4  (stop is EXCLUSIVE)
    print(i)

for i in range(1, 11):     # 1..10 inclusive -> stop = 11
    print(i)

for i in range(0, 10, 2):  # 0,2,4,6,8  (start, stop, step)
    print(i)

total = 0                  # accumulator over a for loop
for n in range(1, 101):
    total += n             # cannot infinite-loop: range is finite
print(total)               # 5050

for _ in range(3):         # '_' = item intentionally ignored
    print("retry")
←Previous: Lesson 2
Next: Lesson 4→

Questions about this lesson: #module-1 on Discord

Found an error in this lesson? Report it

Your progress
Current streak
🔥 0
Lessons marked today
0 / 3
Longest streak
0

Sign in

The bootcamp

  • Curriculum
  • How To
  • Pricing
  • Changelog
  • Future

The community

  • Discord
  • Reviews
  • Contact

Follow

  • Instagram
  • X
  • TikTok
  • Reddit

Legal

  • Terms
  • Privacy
  • Refunds

Coming soon on November 16, 2026

Theme