Skip to content

What to write next — the questions backlog

Level: reference · for whoever is about to write a page

One line: Forty-odd questions Adam asked while reading the Python docs, each routed to the page that answers it, the page that half-answers it, or the page that does not exist yet — because "we should cover formatting" is not a plan and "these eight questions are one page" is.

How to read this page

Three things before you use it.

This is a backlog, not teaching text. Every other page here carries a claim that a program checked (CONTRIBUTING.md). Nothing in the Hook column has been through that. A hook is a claim to verify when the page is written, not a fact this library has established. Expect roughly one in three to come out differently once a program is pointed at it — which is the reason to point a program at it. The handful marked measured were run on 2026-09-07 against Python 3.14.7 while this page was being written, and are quoted so the "do these first" ranking rests on something.

The # column is Adam's own numbering, kept so a question can be found again in the message it came from. It has two collisions — two items numbered 20 and two numbered 35 — left as they arrived rather than silently renumbered.

Status means one of five things:

Status Meaning
written A page here answers it. The row links the page; nothing to do.
partly A page here touches it and stops short. The row says where the gap is.
gap Nothing here answers it. This is the backlog proper.
sibling The answer belongs in another library (encodings ↗, Rust ↗) under the rule in CONTRIBUTING.md — the library that owns the subject owns the page.
answer here A yes/no question that does not need a page. Answered in this file and closed.

Do these five first

Ranked by how many of the questions below one page closes, and by whether the page has a surprise in it — a measured result that contradicts the obvious reading. A page with no surprise is a docs restatement, and the docs are already written.

Page to write Closes Why this one
1 The format mini-languagewritten 2026-09-07 #2 #4 #5 #6 #19 #20a #29 #31, and the two C# tutorials in the tail Eight questions, one grammar. The biggest single gap in the library, and the crosswalk already says so in print: "the method-by-method tour is one of the gaps this page is meant to make visible." The surprise is #19 — formatting an integer with '{:n}' temporarily changes a process-global locale setting, and the docs say outright that this affects other threads. A data race inside a formatting call
2 String literalswritten 2026-09-07 #3 #32, the escape-table item and the U+ vs 0x item in the tail The foundation the format page stands on, and nothing in either library covers Python's literal syntaxwriting a code point ↗ covers the escapes, not the prefixes. The surprise is a contrast Adam's own source hands us: C#'s \x is variable-length and eats following hex digits — \xA1A is one character, not two — where Python's \x is exactly two digits and \xA1A is ¡A. Same escape, two grammars, and one of them has a documented warning attached
3 repr is not strwritten 2026-09-07 #1 #15 #30 Adam's first question, and it cannot be answered without this page: "printable" in Python does not mean "makes ink" — it means repr() will not hex-escape it, which is why str.isprintable() says ' ' is printable and '\t' is not. Measured: string.printable.isprintable() is False, and the docs say that is by design. Also lands str(b'Zoot!')"b'Zoot!'", the quietest bug in the language
4 strip is a set, not a prefixwritten 2026-09-07 #12 #26 The one item in the list that is a bug people ship. Measured: 'Arthur: three!'.lstrip('Arthur: ') is 'ee!' — the argument is a character set, so it ate the thr — while .removeprefix('Arthur: ') is 'three!'. One program, one table, and the Rust contrast is sharp: trim_start_matches takes a pattern, strip_prefix returns an Option
5 A drilling mechanism — katas or a deckwritten 2026-09-08 #4 #33 #35b, and the C# challenge in the tail Shipped as KATAS.md: a ## Practice section on the lesson page that owns the topic, the answer folded and printed by its own program, and tools/check_katas.py holding ten rules with a --selftest that mutates each one. Eight katas, K1–K4 all on bytes, because that is what Adam said three times will not stick. The shape decision is on the page and it went the way the sibling libraries did: a kata lives on its topic's page, never in a numbered folder, because folders are permanent URLs and a sequence is the thing that gets reordered. An Anki deck follows — written as cards rather than generated from these keys, and waiting on two stubs, which KATAS.md says out loud

Progress, 2026-09-08. All five are done, and four method pages with them. Pages 1, 2, 3 and 4 landed on 2026-09-07, and so have all four of the method pages named below. Closed by those seven: #1, #2, #3, #5, #6, #10, #12, #13, #14, #15, #17, #18, #19, #20a, #24, #26, #27, #29, #30, #31, #32, #36 and the two unnumbered escape-table items — twenty-four of the forty, which is the point of grouping them. Row 5 landed as KATAS.md plus a gate rather than as a page, closing #4, #33 and half of the C# challenge: twenty-six of the forty. It was the one of the five that is infrastructure, and the shape decision it needed is written down on that page rather than left in a commit message. What is next is no longer on this ranking — the two open shapes are the rest of the method tour and the two stubs the Anki deck is waiting on.

And the one-in-three warning at the top held. Of the three hooks that page was built on, #17 survived intact, #10 needed sharpening — '日本' is unambiguously four cells, but 'Łódź' is East_Asian_Width=A and has no fixed width at all — and half of #14 was simply wrong: zfill is '{:05d}' only when the value is an int, because format('-42', '05') is '-4200'.

Progress, 2026-09-08. Slicing is not indexing closes #34 and, with Four ways to find it, #36. It is the method-tour item with a provable claim in it rather than a behaviour to describe, and the proof is the page: s[:n] + s[n:] == s run over 187 values of n instead of asserted. The hooks all held — the -0 asymmetry, the truncation of an out-of-range bound, and s[::-1] breaking a decomposed é — and the run turned up one the row did not have: s[-1] is 'y' while s[-1:0] is '', which is the same arithmetic as -0 one step along.

Why not the method tour first. #9 #10 #13 #14 #17 #18 #24 #27 #34 #36 #37 are ten questions about individual str methods, and they are worth one chapter — but they are worth it after the four pages above, because most of them are one paragraph each and a chapter of paragraphs is a docs mirror. The shape that earns its place is a small number of pages each built around a method that does not do what its name says: strip (a set), find (returns -1, and you probably wanted in), translate (a dict keyed by ordinals, which is why maketrans exists), zfill (sign-aware, and only for ASCII 0). That is a page each, not a method each. All four have landedstrip is a set, Four ways to find it, translate is a table and Padding is not alignment — and all four confirmed the shape: grouping methods by how they behave when you are wrong about them produced one page each where a docs paragraph per method would have been. The zfill page took #10 and #17 with it, because make a column line up is one job and all three of those methods measure the width the same wrong way.


Formatting — the biggest gap

Nothing in this library covers str.format, f-strings, or %. Eight of Adam's questions live here, and they are one grammar asked eight ways.

# The question Status Hook to verify
2 Does it make sense to teach format string syntax ↗? writtenThe format mini-language Yes — because it is the same mini-language in four places (str.format, f-strings, format(), __format__) and not the same as %. Rust's format! borrowed the grammar, which makes it the best crosswalk row in the chapter
4 Format spec ↗ as katas or Anki — is there a page? written Both halves now: a page, and K8 drilling it. The spec is [[fill]align][sign][z][#][0][width][grouping][.precision][type] — nine independent slots, so the kata is one drill per slot in order, and then the grammar read backwards: given the output, write the spec. A deck is decided but not yet written; KATAS.md says what it is waiting for
5 Explain the 3.1/3.4 change: '{} {}' vs '{0} {1}'; and !s !r !a written — conversion flags on repr is not str §6, numbering on the format page §3 Auto-numbering and manual numbering cannot be mixed'{} {0}'.format(a, b) raises ValueError. That is the fact the changelog note leaves out, and it is the one that bites. The three conversion flags belong on the repr page instead
6 PEP 682 ↗ negative zero, and the SO answer ↗ — how does Rust deal with it? written — and the answer is that Rust has the same bug and no z option: format!("{:.0}", -0.4) is -0 too. Measured Measured: format(-0.001, '.2f') is '-0.00' — a number that is not negative, displayed as negative, because rounding happened after the sign. format(-0.001, 'z.2f') is '0.00'; the z option is 3.11+. Verify what Rust prints for the same value, and whether it has any equivalent (expected: no — you round first)
19 The n type temporarily sets LC_CTYPE to LC_NUMERIC, "affects other threads" — what is this? written§5 The best hook in the whole backlog: a formatting call that mutates process-global state. It only fires when the separators are non-ASCII or multi-byte and the two locale categories differ, which is why nobody meets it until they do. Pairs with locale and LC_CTYPE in the encodings library. Measured under LC_ALL=C: format(1234, 'n') is '1234' — the example is inert until a locale exists, which is itself the lesson and the reason this example must print its own locale
20a Why is format_map useful? written§6 Because format(**m) copies into a dict and format_map does not — so a dict subclass with __missing__ survives. One paragraph on the format page, not a page
29 The debug specifier f'{number=}' writtenrepr is not str §6 Measured: f'{n=}' is 'n=14.3', and whitespace inside the braces is preserved verbatim — f'{ n - 4 = }' keeps every space. It also silently switches the default conversion to repr(), which is why it belongs half here and half on the repr page
31 Anything to learn from printf-style formatting ↗? written§7 Yes, two things. '%s' % x is not a function call — it is one operator with one right operand, so a tuple is unpacked and a bare tuple must be wrapped, which is the classic TypeError: not all arguments converted. And % is the only one of the four that works on bytes (since 3.5, PEP 461 ↗) — the reason it cannot be retired

Also here, from the unnumbered tail: the C# string interpolation tutorial ↗ and its {0,-20} alignment example are the same grammar with the alignment written inside the field rather than before the type — a good crosswalk row, and the ljust/rjust question (#10) is really "why does a language have both a method and a format spec for this?"


Literals — the other gap with no page

# The question Status Hook to verify
3 What are string literals — is there a page? written String literals. The nearest before it was writing a code point ↗, which covers escapes across four languages but not Python's prefix system. The page to write is the prefix grid — r b f rb br fr, what each disables, and the two that cannot combine (bf does not exist, and there is no such thing as an f-bytes literal)
32 Bytes literals and raw literals — how do other languages do it? written — and Python's raw string turns out to be the weaker one: r"C:\Users\" compiles in Rust and not here Three facts worth a table: a bytes literal may contain only ASCII regardless of the source encoding; a raw literal cannot end in an odd number of backslashes, so r"\" is a syntax error while r"\"" is two characters; and \0 in Python is octal, not a special case. Rust's r#"…"# solves the trailing-backslash problem the other way, with counted hashes
The escape table from the .NET docs, and "I thought Unicode starts with U+ but here is an example with 0x" written§2 and §3 Two separate answers, both short. U+00E9 is a code point name; 0x00E9 is an integer literal that happens to equal it; é is a source-code escape. Same number, three notations, three jobs — the encodings library makes exactly this point and this page should link it rather than repeat it. And the sharp contrast is the .NET warning itself: C#'s \x takes one to four hex digits and keeps eating, so \xA1A is (U+0A1A) and not ¡A, while Python's \x is exactly two digits and '\xA1A' is '¡A'. Measured. A language that made the escape variable-length had to document a footgun; a language that fixed the width did not
C++ string and character literals ↗ — how good or bad is C++ here? partly C is now a column on String literals and in the crosswalk; C++ is still only prose. Worth one more row: C++ has u8 u U L R prefixes and char8_t/char16_t/char32_t/wchar_t, so the prefix says the element type as well as the encoding — which is more information than Python's prefixes carry and more decisions than most callers want

Character classification — mostly answered already

Seven of Adam's questions are already on one page. Is it a letter? covers all twelve is* predicates, the empty-string split, the Rust column and the bytes column.

# The question Status
7 Why is isdecimal useful, does Rust have it, how does it differ from isdigit? writtenIs it a letter? §3 and §"Three kinds of number". int() accepts isdecimal and nothing wider; Rust's char::is_digit takes a radix and is ASCII-only, so it is a false friend
8 Why is islower useful — what are cased characters? written — §5. 'ABC1'.isupper() is True because a digit has no case to disagree with; Dž is titlecase and neither
20b isalnum vs other languages written — §4. It is the union of four predicates, not two, which is why ½ is alphanumeric
21 isalpha vs other languages written — §2, plus the measured Rust grid: is_alphabetic is the Alphabetic property, isalpha is the L* categories, and they disagree in both directions
22 isascii — why still useful with UTF-8? written — §1. It is one of the only two that are True on the empty string. The "why useful" answer to add if the page is ever revisited: it is the cheap gate before an expensive Unicode-aware path
23 isspace — compare to other languages written — §1 and §7, plus what ends a line. Python counts U+001CU+001F as whitespace and Rust's White_Space property does not
1 isprintable / string.printable — how can you print whitespace? writtenrepr is not str. "Printable" means repr() will not escape it, which is why U+0020 is the one printable separator in Unicode and every other space character is not

str and bytes — the pages exist; the drilling does not

# The question Status Hook to verify
16 str.encode — is there a page? written Encode and decode, including every errors= handler and surrogateescape
33 "Still struggle with the bytes concept — maybe katas, anki" written Three pages explained it and a fourth would not have helped, so the answer was repetition rather than exposition: K1–K4 are four katas on one subject — the type boundary, the four-job constructor, the mutable half, and the two doors between them. Every answer is printed by a program and checked in CI on three Pythons. The deck is decided and deferred, with its trigger written down
15 The four str() signatures — is there a page? written repr is not str §5. str(b'Zoot!') is "b'Zoot!'"measured — a five-character bytes object silently becoming an eight-character string with quotes in it. Python has a whole command-line flag (-b) to make it a warning, which is the strongest possible admission that it is a trap
9 join raises TypeError on bytes — show it, and compare Rust, C, ABAP partly The boundary is on str is not bytes; this specific error is not. Note the asymmetry worth measuring: b', '.join([b'a', b'b']) works fine, so join is not "string only" — it is "the separator's own type only", and mixing is what fails
35a "Is it correct?" — the Unicode-vs-bytes summary from the Google exercises answer here Substantially yes. Two nits: "regular Python strings are unicode" is true but the useful phrasing is that a str is a sequence of code points, not of characters (counting characters is the page); and "various libraries such as regular expressions work correctly if passed either type" is the one to distrust — re accepts both and changes what \w and \d mean depending which it got. That is not "works correctly if passed either", it is two different engines behind one API

The method tour — ten questions, worth four pages

All four are now written; the rows below say which page each went to. The recommendation above was to group them rather than write one page per method, and #10, #14 and #17 are the clearest case for it — one page, because all three take their width from len() and go wrong on the same string.

# Method Status Hook to verify
12, 26 lstrip / strip / removeprefix written strip is a set, not a prefix — three differences, not one, and only one filename in four survives rstrip('.txt')
13, 27 maketrans / translate written translate is a table, keyed by ordinal — all four hooks held. The table is a dict of ints on both sides, a value may be a string of any length or None, and any object answering __getitem__ is a table, so a dict subclass with __missing__ is a whitelist filter in one pass. Two things the hook did not predict: the practical reason to use it is that the substitution is one pass, which chained .replace() is not — html.escape in the stdlib carries a comment saying its first line must go first — and 'abc'.translate('xyz') is a silent no-op, because a str is a legal table whose IndexError means "leave it alone"
18, 36 find vs index vs in writtenFour ways to find it Half the hook was wrong, and the measured half is the page. if s.find(x): is wrong twice in one expression — False on a match at position 0, True on every miss — but > -1 is simply correct, and so is >= 0; the other genuinely wrong shape is > 0. The real -1 bug is not a comparison at all: line[line.find('Lancelot'):] is 'm', the last character, with nothing raised. Rust's Option<usize> and byte offset confirmed against the sibling page
24 partition / rpartition writtenFour ways to find it The measurement held: 'Monty Python'.rpartition('-') is ('', '', 'Monty Python') and partition puts the whole string first. Two things the hook did not have: the mirror is deliberate — each one puts a no-separator string in the field its own job wanted — and partition is not "the one that never raises", because an empty separator is ValueError: empty separator
10 ljust / rjust written Padding is not alignment — both hooks hold, and ljust does not even copy: it returns the same object when the value is already too long. The code-point half came out sharper than the hook: '日本' is unambiguously four cells, but two of the four letters in 'Łódź' are East_Asian_Width=A (Ambiguous), so its own width is one cell or two per character depending on the reader's terminal — the string does not carry the answer. center and ^ also disagree by one space on odd widths, which was not in the hook
14 zfill written Padding is not alignment — the sign behaviour holds, and it is narrower than it looks: only the first character counts, so '--42'.zfill(5) is '-0-42'. The "same as the format spec's 0" half is wrong as stated — it holds only when the value is an int. format('-42', '05') is '-4200', because on a str the 0 degrades to a fill character and the default alignment is left
17 expandtabs written Padding is not alignment — every part holds, including the wide-character failure, which is what put it on one page with ljust. One thing the hook missed: only LF and CR reset the tab column, where splitlines() treats eight characters as line boundaries — two against eight in one standard library
34, 36 Slicing, negative indices, s[:n] + s[n:] == s writtenSlicing is not indexing, which with Four ways to find it closes both halves of #36 The invariant holds for every integer, and the page tests it rather than asserting it: 187 values of n across seven strings, plus 10**30, zero failures. Why it cannot fail is slice.indices(), which clamps the bounds before the string is touched — so s[100] is an IndexError and s[:100] is not, the same brackets over two contracts. The one slice that does raise is s[::0], and it is a ValueError. s[-1] is the last character and s[:-0] is '' because -0 is 0 before the slice object exists. And the encodings half, measured on both: s[::-1] reverses code points, so it strands a combining accent at the front of the string and reorders a ZWJ family. The Rust contrast is the sharpest in the language — &s[0..1] on "é" compiles clean and panics at runtime, because Rust slices by byte and enforces the char boundary — slicing by byte ↗ owns it
25 splitlines vs split('\n') written What ends a line — ten boundaries, scanned out of the whole code space, and the three-answer table
11 lower() and Default Case Folding — is there a page? written Lowercasing is not folding. The behaviour held: 'ß'.lower() is 'ß', 'ß'.casefold() is 'ss', and 297 code points fold differently from how they lower. The docs bug was real and is already fixed upstream — section 3.13 of the standard is called Default Case Algorithms, folding is 3.13.3 and conversion is 3.13.2; 3.12/3.13/3.14 attach the folding title to all three methods including upper(), main cites 3.13.2 and 3.13.3 correctly, and the fix has not been backported. Nothing filed — see the page's own section, and the backport question is Adam's. What the hooks got wrong: str.title() gets O'Brien right and Don'T wrong, while string.capwords() does the reverse, so the classic example is backwards
37 * repetition, in / not in, ord() / chr() partly — the string-module half of #37 landed as The string module; ord/chr and * are still open in is now answered in full by Four ways to find it, including what it does on bytes (an int is a needle) and why it is the right call whenever the offset is not used. ord/chr appear throughout counting characters and sorting but are never introduced. The repetition operator's hook: 'Hi' * -8 is '', not an error — so a computed multiplier that goes negative produces an empty column instead of a traceback

Modules — string and codecs

# The question Status Hook to verify
37 The string module constants, capwords, Template, Formatter writtenThe string module The framing held. Measured: the module has exactly 12 public names; string.printable is the other five constants concatenated, which is ASCII 207E plus five controls, and sorting it by code point prints the ASCII chart with every boundary computed. Three hooks came out sharper than written: nine of punctuation's 32 characters are General_Category S*, not P*; capwords also destroys whitespace (' spaced out ''Spaced Out') because it rebuilds the string; and the Template half is a measurement, not a warning — '{0.__init__.__globals__[NAME]}'.format(obj) really does reach a module global in three hops, where '$name.password' substitutes name and leaves nine literal characters. The surprise nobody predicted: Template.idpattern is '(?a:[_a-z][_a-z0-9]*)', and the (?a: is load-bearing — [a-z] under IGNORECASE alone matches KELVIN SIGN, DOTLESS I and LONG S
1 string.printable written repr is not str §4. Measured: it is 100 characters long and string.printable.isprintable() is False, which the docs flag as deliberate
28 The codecs module — is there a page? Any new concepts? written The codecs registry. All three hooks held, one number did not: Python ships eight built-in error handlers, not five, so the one you write is the ninth. Two findings the hook did not predict — the encoder has the same bug and it never raises (utf-16 writes its BOM once per chunk, leaving a U+FEFF sitting in the text as data), and the incremental interface is offered by every codec but honoured by only some (zlib streams, base64 pads every chunk, and 3.11/3.12 decode the result to b'abcde' and report success where 3.13+ raise)

Unicode and the wider reading — mostly the sibling library's

The rule (CONTRIBUTING.md) is that the library owning the subject owns the page. These are encodings subjects; this library links them.

The question Status Where
Add the Unicode core spec ch.2 ↗ link do it RESOURCES.md here and in the encodings library
Is there a page on collation? UTS #10 ↗ partly Sorting is not comparing here is the Python half; sorting and collation ↗ is a stub in the sibling. Neither names UTS #10's three-level model (primary/secondary/tertiary), which is the thing that makes "why is ł next to l but after it" answerable
Deterministic sorting (UTS #10, Deterministic Sorting) gap Sibling. And it is a good gap: a collation that is correct is not necessarily deterministic, so two equal-ranking strings can swap between runs — which breaks pagination, diffs, and any test that sorts. Python's answer is sorted(key=…) stability plus a tiebreak on the raw string
What is different about Unicode regex? UTS #18 ↗ gap Sibling, and it touches this library through re: \w against str is Unicode, against bytes is ASCII, and re.UNICODE vs re.ASCII is the switch. UTS #18's levels 1/2/3 are the vocabulary for saying how Unicode-aware an engine is; the encodings library already measures PCRE2 ↗
Do we have pages for sed, awk, sh? partly sed ↗ and awk ↗ exist. sh does not — and a shell page is a real gap, because the shell is the one language in the set with no string type at all
perlfaq5 ↗ and perlpacktut ↗ — compare ABAP, Rust, Python, C perlfaq5 done, 2026-09-08; perlpacktut still a gap Split, and half closed: perlfaq5 is file I/O and it landed as opening a file, taking flush/unbuffer, counting lines, "why does read-write wipe it out" and "how do I reliably rename a file" as four of its six sections, plus a tell() finding perlfaq5 does not have — a 39-digit cookie for a 6-byte file. perlpacktut is still the better onepack/unpack is Python's struct, Rust's to_be_bytes, C's cast-and-pray, and ABAP's fixed-width fields, which is a four-language page about the same idea and connects to fixed-width byte fields ↗
Grapheme clusters / .NET StringInfo partly Counting characters counts them with a deliberately crude approximation and says so. The proper page is the sibling's a code point is not a character ↗, still a stub. The .NET detail worth stealing: .NET calls it a text element and ships an enumerator, which is one of the few standard libraries that does — Python does not, and that absence is the lesson
Where the boundary with the sibling library runs decided for open(), 2026-09-08 The rule, arrived at twice now and worth stating once: the sibling owns the codec, this library owns the call. For opening a file that means which encoding the default resolves to, UTF-8 mode, PEP 686, a wrong bet surfacing, and EncodingWarning are the sibling's ↗; mode, buffering, the tell() cookie, counting lines, replace-by-rename and the four-language table are here. Both stubs said nearly the same four things before this; both now say which half is theirs and link the other. Apply the same test to the normalization row below — and note it may not come out the same way, because a normal form is a codec-flavoured subject where a file handle is not
Normalization partly, and a boundary to settle A stub here; written in the sibling as 04_Python/normalization ↗. Two libraries, one Python page, and the roadmap already flags this as an open question. Decide it before writing: the sibling's Python chapter should probably point here
Noncharacters and private use (FAQ ↗) partly Named in the sibling's preparing a string ↗; no page. The hook: a noncharacter is not invalidU+FFFE is a legal code point that UTF-8 will happily encode, and the reason the BOM works is that its byte-swapped twin is one of them
The UTF FAQ ↗ — anything new? written Byte order and the BOM ↗ covers it, including the U+FFFE mirror argument
Ordinal string operations (.NET StringComparison.Ordinal) — do we have it? written, 2026-09-08 Comparison has a mode. The four résumé spellings came out as predicted — four values, six Falses — and the find table was better than expected: the ASCII needle sum lands at index 2 in two spellings and 3 in the other two, so an offset moves for a letter you did not search for. The NUL pair went the other way and improved the page: Python's == says not-equal as expected, but locale.strxfrm neither ignores nor compares the NUL — it raises ValueError, which is a third behaviour and the right one. Two extras found while measuring: lower() and casefold() disagree on 297 code points, and on 172 of them (all Cherokee) casefold() returns an uppercase letter; and re.IGNORECASE is a third case-insensitive answer agreeing with neither
String normalization (MS docs ↗) partly Same row as normalization above
Why UTF-7? (.NET UTF7Encoding) gap Short and worth it. UTF-7 exists because 1990s mail gateways were 7-bit, and it is obsolete and dangerous: it is deprecated in .NET, and it is a live XSS vector because one string has several UTF-7 spellings. The sibling already uses it in parser differentials ↗; the missing piece is the why
The .NET encoding overview ↗, best practices for strings ↗, display data ↗, character encoding intro ↗ reference Read for examples to borrow, not for a page. The one structural idea worth importing is .NET's insistence that every string API takes an explicit comparison mode — the opposite of Python, where the default is invisible. Compare against Python text in practice ↗
PowerShell character encoding ↗ — how good or bad? reference One row, not a page. The honest answer is "much better since 6.0, and the version boundary is the whole story": Windows PowerShell 5.1 defaults to UTF-16LE for > redirection and the ANSI code page elsewhere; PowerShell 7 defaults to BOM-less UTF-8 everywhere. Verify before writing
Intel HEX ↗ — useful for the tri-format kata? sibling Yes, one idea in particular. The sibling's tribit ↗ is a hand-rolled encoding; Intel HEX is a shipped one with the two things a hand-rolled format usually lacks — a record type and a checksum — and its checksum is two's-complement of the sum of every byte, which is four lines to implement and instantly makes a corrupted file detectable. That is the lesson to borrow: the format is unremarkable, the framing discipline is not

Exercises

# The question Status Hook
4, 33 Katas or Anki written 2026-09-08 KATAS.md — eight katas, the ## Practice convention in CONTRIBUTING.md, and tools/check_katas.py with a selftest. Anki decided: it follows, as cards rather than as a rendering of these
35b Rewrite the Google Python exercises ↗ in C, ABAP and Rust, cross-referencing solutions gap, and check the licence first The idea is good and it is exactly this library's crosswalk instinct. But the material is someone else's: CONTRIBUTING.md says a topic may be taken and the prose, examples and data must be written here, and the exercise files carry an Apache 2.0 header — so the shape that works is our own exercises on the same topics, credited, not a port of theirs. Decide that before writing any of it
The C# formatting challenge ↗ — worth doing as a kata? partly The format was worth copying and K8 now does it: the second half of that kata is a fixed input, a fixed expected output, and the reader has to produce the format string. Four rows so far. Extending it is cheap — a row in the answer key's table, no new file — and the licence note in row 35b applies: our own inputs, not theirs

See also

  • Roadmap — what is written and what is a stub, page by page
  • The crosswalk — the same ideas in Rust, C and ABAP; the place a new comparison row lands
  • CONTRIBUTING.md — what it takes to turn a row above into a page
  • The sibling's backlog ↗ — the same job, done for the encodings library, and the model this page follows