View all services
Talk to QA Advisor
Free tool · Test data

Edge case strings for input testing

The inputs that break validation, storage, search and display, grouped by what they test. Each one shows its length four different ways, because the disagreement between those numbers is exactly where field limits fail.

Runs entirely in your browser. Nothing you enter is sent to a server.

95 strings in 9 groups. Invisible and control characters are shown as labelled markers, and the copy button copies the real characters.

String length and byte counter

Paste any string to see its length four ways, and any invisible characters it contains.

–visible10code points13UTF-16 units24UTF-8 bytes
Café·👨⟨U+200D⟩👩⟨U+200D⟩👧

Custom length generator

Exactly the number of visible characters you need, in a character set that stresses how each layer counts them.

Character set
–visible256code points256UTF-16 units256UTF-8 bytes

Length and emptiness

Boundaries on length are where validation, truncation and storage limits disagree.

  • Empty string

    (empty)
    –visible0code points0UTF-16 units0UTF-8 bytes

    Distinct from null and from absent. Many APIs treat the three differently and inconsistently.

  • Single character

    a
    –visible1code points1UTF-16 units1UTF-8 bytes

    The minimum non-empty input. Catches off-by-one errors on minimum length rules.

  • Single space

    ·
    –visible1code points1UTF-16 units1UTF-8 bytes

    Passes a "not empty" check and fails a "not blank" one.

  • 255 characters

    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…
    –visible255code points255UTF-16 units255UTF-8 bytes

    A common database column limit. Test at the limit and one over.

  • 256 characters

    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…
    –visible256code points256UTF-16 units256UTF-8 bytes

    One past 255. Should be rejected cleanly, not truncated silently.

  • 1,000 characters

    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…
    –visible1000code points1000UTF-16 units1000UTF-8 bytes

    Exposes layouts that assume short text and fields with no maximum at all.

  • 10,000 characters

    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…
    –visible10000code points10000UTF-16 units10000UTF-8 bytes

    Large enough to find missing server-side limits and slow rendering.

Whitespace and invisible characters

Characters that are present but unseen. They break equality checks, trimming and search.

  • Leading space

    ·leading
    –visible8code points8UTF-16 units8UTF-8 bytes

    Is it trimmed on save, on compare, both or neither?

  • Trailing space

    trailing·
    –visible9code points9UTF-16 units9UTF-8 bytes

    The classic cause of a login that fails for a correctly typed username.

  • Only spaces

    ·····
    –visible5code points5UTF-16 units5UTF-8 bytes

    Should a field of spaces count as filled in? Usually not.

  • Tab

    a⇥b
    –visible3code points3UTF-16 units3UTF-8 bytes

    Breaks tab-separated exports and some log parsers.

  • Line feed

    line one↵line two
    –visible17code points17UTF-16 units17UTF-8 bytes

    Single-line fields that accept a newline corrupt CSVs and emails.

  • Carriage return and line feed

    line one␍↵line two
    –visible18code points18UTF-16 units18UTF-8 bytes

    Windows line ending. Compare with plain line feed handling.

  • Non-breaking space

    a⟨U+00A0⟩b
    –visible3code points3UTF-16 units4UTF-8 bytes

    Looks like a space, is not one. Java’s trim() keeps it, and it defeats splitting on spaces.

  • Zero-width space

    a⟨U+200B⟩b
    –visible3code points3UTF-16 units5UTF-8 bytes

    Invisible. Two strings that look identical compare unequal.

  • Zero-width space on its own

    ⟨U+200B⟩
    –visible1code points1UTF-16 units3UTF-8 bytes

    Just U+200B. A field that looks empty but is not, so it tests required-field checks and trimming.

  • Zero-width joiner

    a⟨U+200D⟩b
    –visible3code points3UTF-16 units5UTF-8 bytes

    Invisible, and also the glue inside multi-person emoji.

  • Byte order mark

    ⟨U+FEFF⟩text
    –visible5code points5UTF-16 units7UTF-8 bytes

    Arrives from files saved by some editors. Breaks the first field of a CSV import.

  • Soft hyphen

    soft⟨U+00AD⟩hyphen
    –visible11code points11UTF-16 units12UTF-8 bytes

    Invisible unless the line wraps there. Breaks search and exact matching.

Unicode and international text

Real names and addresses. Most of these are ordinary text somewhere in the world.

  • Accented Latin

    Crème brûlée
    –visible12code points12UTF-16 units15UTF-8 bytes

    Basic test of whether the stack is UTF-8 end to end.

  • Decomposed é

    Café
    –visible5code points5UTF-16 units6UTF-8 bytes

    e followed by a combining accent. Looks identical to the precomposed form but compares unequal unless normalised.

  • Precomposed é

    Café
    –visible4code points4UTF-16 units5UTF-8 bytes

    Same visible text as the line above. Test that both match the same record.

  • German sharp s

    Straße
    –visible6code points6UTF-16 units7UTF-8 bytes

    Upper-cases to STRASSE, so the length changes on case conversion.

  • Turkish dotted and dotless i

    İstanbul ılık
    –visible13code points13UTF-16 units16UTF-8 bytes

    Case mapping is locale dependent. toLowerCase() in a Turkish locale gives a different result.

  • Greek with final sigma

    Σίσυφος
    –visible7code points7UTF-16 units14UTF-8 bytes

    Sigma has a different form at the end of a word, which naive case conversion gets wrong.

  • Japanese

    日本語テキスト
    –visible7code points7UTF-16 units21UTF-8 bytes

    Multi-byte characters. Truncating by bytes can split one in half.

  • Korean

    한국어
    –visible3code points3UTF-16 units9UTF-8 bytes

    Precomposed Hangul syllables, three bytes each in UTF-8.

  • Hindi

    नमस्ते
    –visible6code points6UTF-16 units18UTF-8 bytes

    Conjuncts and vowel signs: what looks like four letters is six code points.

  • Tamil

    தமிழ்
    –visible5code points5UTF-16 units15UTF-8 bytes

    Combining marks change both width and cursor behaviour.

  • Arabic

    مرحبا بالعالم
    –visible13code points13UTF-16 units25UTF-8 bytes

    Right-to-left. Check alignment, cursor position and truncation direction.

  • Hebrew

    שלום עולם
    –visible9code points9UTF-16 units17UTF-8 bytes

    Right-to-left with different shaping rules from Arabic.

  • Mixed direction

    Order 123 مرحبا 456
    –visible19code points19UTF-16 units24UTF-8 bytes

    Bidirectional text. Numbers inside right-to-left text are where layouts usually break.

  • Right-to-left override

    invoice⟨U+202E⟩txt.exe
    –visible15code points15UTF-16 units17UTF-8 bytes

    Displays reversed. The classic way to disguise a file name, so filename display should neutralise it.

  • Mathematical letters

    𝕳𝖊𝖑𝖑𝖔
    –visible5code points10UTF-16 units20UTF-8 bytes

    Outside the basic plane: each letter is two UTF-16 units, so length() reports double.

  • Stacked combining marks

    Z̀́̂ã̄l̅̆ġ̈ỏ̊
    –visible16code points16UTF-16 units27UTF-8 bytes

    Many marks on one letter. Overflows line height and breaks fixed-height containers.

Emoji

One visible symbol can be many code points. This is where length limits go wrong.

  • Simple emoji

    😀
    –visible1code points2UTF-16 units4UTF-8 bytes

    One symbol, two UTF-16 units, four UTF-8 bytes.

  • With skin tone

    👍🏽
    –visible2code points4UTF-16 units8UTF-8 bytes

    One visible symbol made of two code points.

  • Family (joined sequence)

    👨⟨U+200D⟩👩⟨U+200D⟩👧⟨U+200D⟩👦
    –visible7code points11UTF-16 units25UTF-8 bytes

    One visible symbol, seven code points, eleven UTF-16 units, twenty-five bytes.

  • Flag

    🇮🇳
    –visible2code points4UTF-16 units8UTF-8 bytes

    Two regional indicator letters that render as one flag. Splitting them shows two letters.

  • Keycap

    1️⃣
    –visible3code points3UTF-16 units7UTF-8 bytes

    A digit, a variation selector and a combining keycap.

  • Emoji inside text

    Great work 🎉 team
    –visible17code points18UTF-16 units20UTF-8 bytes

    Check counters, truncation with an ellipsis, and search.

Numbers as text

Values that parse to something other than what was typed, or cannot be stored where they are put.

  • Zero

    0
    –visible1code points1UTF-16 units1UTF-8 bytes

    Falsy in JavaScript. A truthiness check treats it as missing.

  • Negative zero

    -0
    –visible2code points2UTF-16 units2UTF-8 bytes

    Parses, equals zero, and still shows its sign in some formatting.

  • Leading zeros

    00123
    –visible5code points5UTF-16 units5UTF-8 bytes

    Postcodes and account numbers lose their zeros when stored as numbers.

  • Exponent

    1e3
    –visible3code points3UTF-16 units3UTF-8 bytes

    Many parsers read this as 1000.

  • Decimal comma

    1,5
    –visible3code points3UTF-16 units3UTF-8 bytes

    One and a half in most of Europe. Breaks parsers expecting a dot.

  • Padded number

    ·42·
    –visible4code points4UTF-16 units4UTF-8 bytes

    Does the parser trim, reject or accept partially?

  • Hexadecimal

    0x1F
    –visible4code points4UTF-16 units4UTF-8 bytes

    Some parsers accept it, most validators should not.

  • 32-bit integer maximum

    2147483647
    –visible10code points10UTF-16 units10UTF-8 bytes

    The largest signed 32-bit integer. Test it and one more.

  • 32-bit overflow

    2147483648
    –visible10code points10UTF-16 units10UTF-8 bytes

    One past the limit. Overflows an int column or wraps negative.

  • Beyond JavaScript safe integer

    9007199254740993
    –visible16code points16UTF-16 units16UTF-8 bytes

    Silently rounds to 9007199254740992 in JavaScript. Fatal for IDs.

  • Very large

    99999999999999999999
    –visible20code points20UTF-16 units20UTF-8 bytes

    Exceeds a 64-bit integer.

  • Not a number

    NaN
    –visible3code points3UTF-16 units3UTF-8 bytes

    Parses to NaN, which is not equal to itself.

  • Infinity

    Infinity
    –visible8code points8UTF-16 units8UTF-8 bytes

    parseFloat accepts it. Most business rules should not.

Reserved words and type confusion

Text that some layer of the stack treats as a keyword rather than as a value.

  • null

    null
    –visible4code points4UTF-16 units4UTF-8 bytes

    A real surname. Systems that serialise and re-parse turn it into an actual null.

  • NULL

    NULL
    –visible4code points4UTF-16 units4UTF-8 bytes

    Same problem in upper case, common in database exports.

  • undefined

    undefined
    –visible9code points9UTF-16 units9UTF-8 bytes

    The text a JavaScript bug writes into a field. Check it is not treated as empty.

  • None

    None
    –visible4code points4UTF-16 units4UTF-8 bytes

    Python's null, written as text by careless serialisation.

  • true

    true
    –visible4code points4UTF-16 units4UTF-8 bytes

    Coerced to a boolean by loose JSON or YAML handling.

  • Norway problem

    NO
    –visible2code points2UTF-16 units2UTF-8 bytes

    YAML 1.1 parses NO as false. A country code that turns into a boolean.

  • Empty array

    []
    –visible2code points2UTF-16 units2UTF-8 bytes

    Parsed as JSON by some frameworks rather than stored as text.

  • Windows reserved name

    CON
    –visible3code points3UTF-16 units3UTF-8 bytes

    Cannot be used as a file name on Windows. Matters for uploads and exports.

Escaping probes

Benign probes that reveal whether input is encoded before output. Use them on systems you are authorised to test.

  • Apostrophe in a name

    O'Brien
    –visible7code points7UTF-16 units7UTF-8 bytes

    A real surname that still breaks hand-built SQL and some JSON handling.

  • Double quotes

    Say "hello"
    –visible11code points11UTF-16 units11UTF-8 bytes

    Breaks unescaped HTML attributes and hand-built JSON.

  • Backslash

    C:\Users\test
    –visible13code points13UTF-16 units13UTF-8 bytes

    Escape character in most languages. Often doubled or swallowed.

  • SQL tautology

    ' OR '1'='1
    –visible11code points11UTF-16 units11UTF-8 bytes

    If this changes a query result, input reaches SQL unparameterised.

  • SQL comment

    admin'--
    –visible8code points8UTF-16 units8UTF-8 bytes

    Probes whether trailing query text can be cut off.

  • Script tag

    <script>alert(1)</script>
    –visible25code points25UTF-16 units25UTF-8 bytes

    Should render as visible text, never execute.

  • Attribute break-out

    "><img src=x onerror=alert(1)>
    –visible30code points30UTF-16 units30UTF-8 bytes

    Tests encoding inside HTML attributes, which is easy to miss.

  • Template expression

    {{7*7}} ${7*7}
    –visible14code points14UTF-16 units14UTF-8 bytes

    If 49 appears, a template engine is evaluating user input.

  • Path traversal

    ../../etc/passwd
    –visible16code points16UTF-16 units16UTF-8 bytes

    Tests file path handling on downloads and uploads.

  • Null byte

    report.pdf⟨U+0000⟩.exe
    –visible15code points15UTF-16 units15UTF-8 bytes

    Some layers truncate at the null byte. Browsers may truncate it when copying.

  • Header injection

    value␍↵X-Injected: 1
    –visible20code points20UTF-16 units20UTF-8 bytes

    Line breaks in a value that reaches an HTTP header or an email.

  • Spreadsheet formula

    =1+1
    –visible4code points4UTF-16 units4UTF-8 bytes

    Executes when an export is opened in a spreadsheet. Prefix such cells with an apostrophe.

Email addresses

Valid addresses that strict validators reject, and invalid ones lenient validators accept. All use reserved test domains.

  • Plus addressing

    [email protected]
    –visible26code points26UTF-16 units26UTF-8 bytes

    Valid and widely used. Rejecting it loses real users.

  • Apostrophe

    o'[email protected]
    –visible19code points19UTF-16 units19UTF-8 bytes

    Valid. Rejected by an alarming number of sign-up forms.

  • Two-letter TLD

    [email protected]
    –visible12code points12UTF-16 units12UTF-8 bytes

    Valid. Breaks validators that require three or more characters.

  • Long TLD

    [email protected]
    –visible23code points23UTF-16 units23UTF-8 bytes

    Valid. Breaks validators that cap the TLD at four characters.

  • Subdomain

    [email protected]
    –visible25code points25UTF-16 units25UTF-8 bytes

    Valid, with several labels.

  • Upper case

    [email protected]
    –visible16code points16UTF-16 units16UTF-8 bytes

    Should match the lower-case account. Domain is case-insensitive; local part usually treated so.

  • Quoted local part

    "john doe"@example.com
    –visible22code points22UTF-16 units22UTF-8 bytes

    Technically valid. Decide deliberately whether to accept it.

  • IP literal

    user@[192.0.2.1]
    –visible16code points16UTF-16 units16UTF-8 bytes

    Valid, using a documentation-only IP range.

  • Missing at sign

    plainaddress
    –visible12code points12UTF-16 units12UTF-8 bytes

    Invalid. Should be rejected.

  • Consecutive dots

    [email protected]
    –visible23code points23UTF-16 units23UTF-8 bytes

    Invalid. Commonly accepted by lenient validators.

  • Trailing dot in local part

    [email protected]
    –visible18code points18UTF-16 units18UTF-8 bytes

    Invalid.

  • Space inside

    first·[email protected]
    –visible22code points22UTF-16 units22UTF-8 bytes

    Invalid unless quoted.

Dates and times

The values date handling gets wrong, usually only in production.

  • Leap day

    2024-02-29
    –visible10code points10UTF-16 units10UTF-8 bytes

    Valid. Test age calculations and anniversaries in non-leap years.

  • Invalid leap day

    2023-02-29
    –visible10code points10UTF-16 units10UTF-8 bytes

    Invalid. Some parsers roll it over to 1 March silently.

  • Unix epoch

    1970-01-01T00:00:00Z
    –visible20code points20UTF-16 units20UTF-8 bytes

    Zero. Often a sign of a null date that was converted.

  • 2038 overflow

    2038-01-19T03:14:08Z
    –visible20code points20UTF-16 units20UTF-8 bytes

    One second past the limit of a signed 32-bit timestamp.

  • Far future

    9999-12-31
    –visible10code points10UTF-16 units10UTF-8 bytes

    A common "no expiry" sentinel. Check it is displayed sensibly.

  • Zero date

    0000-00-00
    –visible10code points10UTF-16 units10UTF-8 bytes

    Accepted by some databases, invalid everywhere else.

  • Ambiguous order

    03/04/2025
    –visible10code points10UTF-16 units10UTF-8 bytes

    3 April or 4 March depending on locale.

  • Daylight saving gap

    2025-03-30T02:30:00
    –visible19code points19UTF-16 units19UTF-8 bytes

    Does not exist in most of Europe: clocks skip from 02:00 to 03:00.

  • Timezone offset

    2025-06-01T23:30:00+05:30
    –visible25code points25UTF-16 units25UTF-8 bytes

    Already 2 June in UTC. Tests date boundaries across zones.

How to use

How to use the Edge case string generator

  1. 1To measure a string you already have, paste it into the measure box. It shows the visible characters, code points, UTF-16 units and UTF-8 bytes.
  2. 2Pick a group, or leave every group showing.
  3. 3Copy a string with its button. The exact characters are copied, including the invisible ones you cannot see on screen.
  4. 4Paste it into the field under test and submit. Then check the value everywhere it travels: the API response, the database row, the screen that displays it, and any export.
  5. 5For length limits, use the custom length generator to produce exactly the number of characters you need, in a character set that stresses how your storage counts them.

Length is four different numbers

A single family emoji is one character to the person typing it, seven Unicode code points, eleven UTF-16 code units and twenty-five bytes in UTF-8. Every layer of a typical stack counts with a different one of those, which is why a field can accept a value in the browser and reject it at the database.

HTML maxlength counts UTF-16 code units, as does String.length in JavaScript and Java. MySQL with utf8mb4 and PostgreSQL both count characters in a VARCHAR(n), meaning code points. SQL Server NVARCHAR(n) counts UTF-16 code units. Oracle VARCHAR2 counts bytes unless the column was declared with character semantics. A 255-character limit therefore means four different things depending on which layer enforces it, and the bug is always in the gap between two of them.

Test every limit at the boundary, one under and one over, and then repeat the boundary test with multi-byte characters. A limit that holds for 255 ASCII letters frequently fails for 255 accented letters or 128 emoji.

Invisible characters survive the obvious defences

Trimming is not a cleaning step people can rely on. In JavaScript, trim() removes the non-breaking space and the byte order mark but not the zero-width space. Java String.trim() removes only characters at or below U+0020, so it leaves the non-breaking space in place. Python strip() removes the non-breaking space and leaves the zero-width space. The zero-width space survives all three.

The practical consequence is that two values which look identical on screen compare unequal, so a user cannot log in with a username they can see is correct, or a search finds nothing for a term that is plainly in the record. Paste the whitespace group into any field that is later compared or searched, and check that the comparison still holds.

Normalisation decides whether two names match

The letter é can be stored as one precomposed code point or as an e followed by a combining accent. Both render identically. Unless the application normalises to one form, usually NFC, before storing and comparing, the same name entered on two different devices produces two different records.

Decomposed text arrives from more places than people expect: older macOS file systems, text copied from some PDFs and certain input methods. The precomposed and decomposed pair in the Unicode group are there to test exactly this: both should find the same record.

Escaping probes are a test of encoding, not an attack

The escaping group contains the standard defensive probes: a script tag, an attribute break-out, a SQL tautology, a template expression. They are deliberately benign. They exist to confirm that the application treats input as text, and a pass looks the same for every one of them: the value is stored exactly as typed and displayed back literally, never interpreted.

If a template expression comes back as 49, or a script tag disappears from the rendered page instead of showing as text, input is reaching an interpreter. Use these only on systems you are authorised to test, and treat any of them behaving differently from plain text as a finding to report.

Follow the value all the way round

Most of these strings pass the form and fail later. The full round trip is the test: submit the value, read it back through the API, check the database row, view it on every screen that displays it, export it to CSV and open the file, and search for it. A trailing space trimmed on save but not on comparison, or an emoji stored correctly and truncated in an export, only appears when the value is followed the whole way.

QAble service

Functional testing services

QAble tests input handling the way it fails in production: the full round trip from form to database to export, in every character set your users actually type.

Explore the service
FAQ

Frequently asked questions

What is an edge case string?

An input chosen because it sits at a boundary or exercises an unusual path: an empty value, a value exactly at a length limit, characters outside the basic Latin set, invisible characters, or text that some layer of the stack treats as code. They find the defects that ordinary test data, which tends to be short ASCII names, never reaches.

Why does one emoji count as two characters in JavaScript?

JavaScript strings are sequences of UTF-16 code units, and characters outside the Basic Multilingual Plane, which includes most emoji, need two code units each. String.length reports code units, not visible characters, so a single emoji has a length of two and a family emoji has a length of eleven.

What is the difference between trimming and normalising?

Trimming removes whitespace from the ends of a string. Normalising converts equivalent Unicode sequences into one canonical form, such as turning an e plus a combining accent into a single é. They solve different problems, and an application usually needs both before it stores or compares user input.

Are the escaping strings safe to use?

Yes. They are the standard benign probes used to check output encoding, and none of them does harm if it executes: the script calls alert(1) and the template expression is arithmetic. Use them only on applications you are authorised to test.

How should I test a field with a 255-character limit?

Test 254, 255 and 256 characters of plain ASCII, then 255 characters of accented letters and enough emoji to exceed the limit in UTF-16 units while staying under it in visible characters. If the limit is enforced in more than one place, those are the cases where the layers disagree.

How do I copy a zero-width space?

Use the copy button next to Zero-width space on its own, in the whitespace group. It copies the single character U+200B, which is almost impossible to select by hand because it has no width on screen.

How do I count the bytes in a string?

Paste it into the measure box at the top of the tool. It shows the length four ways: visible characters, Unicode code points, UTF-16 code units and UTF-8 bytes. Those are the four numbers different layers of a stack mean when they say length.

Is anything I copy sent to QAble?

No. The strings are built into the page and every count is calculated in your browser. Nothing is sent to a server.

Need the edge cases found for you?

QAble testers work through the inputs, payment paths and data that break products in production, before your users find them.

Talk to QA Advisor