Quick answer: For a simple run-length encoding, group adjacent equal characters and store each character with its consecutive count. This reduces repeated runs but is not the same as general-purpose byte compression such as zlib.

String compression in Python usually means replacing repeated characters with a shorter representation. A common beginner-friendly technique is run-length encoding. It reads a string from left to right, counts adjacent repeated characters, and writes each character with its count. For example, "aaabbc" can become "a3b2c1".
This method is useful for learning loops, indexes, grouping, input checks, and reversible transforms. It is not a universal compression system like gzip. Run-length encoding works best when the text has long repeated runs, such as simple masks, game boards, bitmap-like data, or generated labels. It can make normal prose longer because most letters do not repeat many times in a row. For an implementation-focused walkthrough of the same compression method, Run Length Encoding in Python Guide develops the value-and-count representation step by step.
The official itertools.groupby documentation explains the grouped iterator used later in this guide. The Python re documentation is useful when validating encoded text with a pattern.
Before writing code, decide whether the compressed form must always be shorter. Some interview questions return the original string when compression does not save space. Other lessons always return the encoded form so the result is easier to inspect. Also decide whether counts may have more than one digit. Real examples need to handle "aaaaaaaaaaaa" as "a12", not as a single-digit count.
Compress A String With A Loop
The clearest approach keeps the current character and a count. When the next character changes, append the finished run to a list and start counting the new character.
def compress_text(text):
if not text:
return ""
parts = []
current = text[0]
count = 1
for char in text[1:]:
if char == current:
count += 1
else:
parts.append(current + str(count))
current = char
count = 1
parts.append(current + str(count))
return "".join(parts)
print(compress_text("aaabbcccc"))
This prints a3b2c4. The final append after the loop matters because the last run has no later character to trigger the change branch.
Building a list and joining it is better than repeatedly extending one string inside the loop. Strings are immutable, so list collection keeps the example efficient and readable.
Return The Original When It Is Shorter
Many coding challenges ask for compression only when it saves space. In that case, compute the encoded form, compare lengths, and return whichever string is shorter.
def compress_text(text):
if not text:
return ""
parts = []
current = text[0]
count = 1
for char in text[1:]:
if char == current:
count += 1
else:
parts.append(current + str(count))
current = char
count = 1
parts.append(current + str(count))
return "".join(parts)
def compress_if_shorter(text):
encoded = compress_text(text)
if len(encoded) < len(text):
return encoded
return text
print(compress_if_shorter("abcdef"))
print(compress_if_shorter("aaaaabbbbcc"))
The first call returns the original text because a1b1c1d1e1f1 would be longer. The second call returns a compressed value because repeated runs create a clear saving.
This policy is useful in user-facing code. It avoids surprising output when the source text has no useful repetition, while still keeping the compression helper reusable.

Decompress Run-Length Encoded Text
A reversible format is easier to test. To decompress, read a character, collect the digits after it, convert those digits to a count, and repeat the character.
def decompress_text(encoded):
output = []
index = 0
while index < len(encoded):
char = encoded[index]
index += 1
digits = []
while index < len(encoded) and encoded[index].isdigit():
digits.append(encoded[index])
index += 1
if not digits:
raise ValueError("missing count")
output.append(char * int("".join(digits)))
return "".join(output)
print(decompress_text("a3b2c4"))
This prints aaabbcccc. The inner loop allows counts like 12 or 104, so the decoder is not limited to short runs.
The function raises ValueError if a character has no count after it. That makes malformed input fail clearly instead of producing a silent wrong result.
Use itertools.groupby For Compact Code
itertools.groupby() groups adjacent equal values. It can shorten the compression function while keeping the same run-length encoding idea.
from itertools import groupby
def compress_with_groupby(text):
parts = []
for char, group in groupby(text):
count = sum(1 for _ in group)
parts.append(char + str(count))
return "".join(parts)
print(compress_with_groupby("zzzzaaaabb"))
This prints z4a4b2. The grouping is adjacent only, so "aaba" becomes a2b1a1, not a3b1.
Use this version when the team already understands iterators. For a first explanation, the manual loop is often easier because every state change is visible.

Validate Encoded Input Before Decoding
If encoded text comes from a file, form, or another program, validate it before decoding. A regular expression can confirm that the string is made from repeated character-and-count pairs.
import re
pattern = re.compile(r"(?:[A-Za-z]\d+)+\Z")
def is_encoded_text(text):
return bool(pattern.fullmatch(text))
print(is_encoded_text("a3b2c4"))
print(is_encoded_text("abc"))
This pattern accepts letters followed by one or more digits. Adjust the character range if your format needs spaces, punctuation, Unicode text, or escape rules.
Validation and decoding should agree on the same format. If the pattern allows a character, the decoder must know how to handle that character without ambiguity.

Compress A File-Friendly String
For small text files, read the text, compress it, and write the encoded result. This example uses explicit UTF-8 encoding so behavior is consistent across systems.
from pathlib import Path
from tempfile import TemporaryDirectory
def compress_text(text):
if not text:
return ""
parts = []
current = text[0]
count = 1
for char in text[1:]:
if char == current:
count += 1
else:
parts.append(current + str(count))
current = char
count = 1
parts.append(current + str(count))
return "".join(parts)
def compress_file(source_path, target_path):
text = Path(source_path).read_text(encoding="utf-8")
encoded = compress_text(text)
Path(target_path).write_text(encoded, encoding="utf-8")
return len(text), len(encoded)
with TemporaryDirectory() as folder:
source = Path(folder) / "input.txt"
target = Path(folder) / "output.rle"
source.write_text("aaabbcccc", encoding="utf-8")
original_size, encoded_size = compress_file(source, target)
print(original_size, encoded_size)
This is a demonstration, not a replacement for standard archive formats. For production storage, compare against built-in and platform tools that handle binary data, metadata, and broad file types.
The main testing rule is to check round trips. If decompress_text(compress_text(value)) equals the original for empty strings, single characters, long runs, and mixed text, the pair is likely behaving as intended.
String compression in Python is most useful as a focused exercise in stateful loops and format design. Start with the manual loop, add the shorter-return policy if the compressed value is user-facing, and include decompression when the result must be read back later.
Run-Length Encode Adjacent Runs
Run-length encoding replaces a run such as aaaa with a symbol and its count. It is useful when repeated values are adjacent, but it can make short or high-entropy text larger because counts and separators add overhead. Define the output format before writing a decoder.
from itertools import groupby
def encode_runs(text):
parts = []
for character, group in groupby(text):
count = sum(1 for _ in group)
parts.append(f"{count}{character}")
return "".join(parts)
print(encode_runs("aaabbc")) # 3a2b1c

Decode With A Clear Contract
A decoder must know where the count ends and the symbol begins. A format that allows digits in the original text needs escaping or a structured representation, such as pairs of integers and characters. Test empty input, one-character runs, large counts, and symbols that look like delimiters.
Choose The Right Compression Tool
Run-length encoding is an algorithmic teaching example and can work well for repeated pixels, tokens, or telemetry. For general bytes, compare a standard compressor such as zlib and measure the compressed size and decompression cost on representative data. Compression does not remove the need for an encoding and integrity policy.
Frequently Asked Questions
How do I compress a string in Python?
For a teaching-level run-length encoding, group adjacent equal characters and store each character with its consecutive count; for general bytes, compare zlib.
What is run-length encoding?
Run-length encoding replaces adjacent repetitions such as aaaa with a symbol and count such as 4a.
Does run-length encoding always make a string smaller?
No. Short or high-entropy strings can become larger because counts and separators add overhead.
How should I test a string compression algorithm?
Test empty input, one-character runs, digits and delimiters, large counts, round-trip decoding, and compressed size on representative data.
It says “‘i’ is not defined” for the Simple Loop compression program when trying to compress 1 character.
Can you try using a different code? Practically it’s hard to compress 1 character unless you have zero-width space characters. Let me know if you still need help with anything.
Regards,
Pratik
how can i make my for loop do it but in a way where it does say 1 for example abbc compressed would be ab2c
Following code will do –
new_string = ""string = "pythooonnnpool"
count = 1
for i in range(len(string)-1):
if string[i] == string[i+1]:
count = count + 1
else:
new_string = new_string + string[i] + (str(count) if count > 1 else "")
count = 1
new_string = new_string + string[i+1] + (str(count) if count > 1 else "")
print(new_string)