📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

What are raw string literals in C# 11? Explain the triple-quote syntax, how indentation is removed, including quotes inside the text, interpolated raw strings with multiple dollar signs, UTF-8 raw strings, and where they help in real code such as JSON, SQL and test fixtures.

Raw string literals: syntax, indentation rules, interpolation with $$ and practical uses.

Junior Theory Beginner csharp-11 json raw-string-literals strings testing

🎯 Key points

  • Raw string literals (C# 11) use three or more double quotes and have no escape sequences
  • In multi-line raw strings the whitespace before the closing quotes is removed from every line
  • Use more quotes in the delimiters to include three quotes in the text
  • The number of dollar signs sets how many braces open an interpolation hole; $$ suits JSON
  • The u8 suffix produces a compile-time UTF-8 ReadOnlySpan of bytes
  • They make JSON, SQL, regex and test fixtures readable
  • They do not make interpolated SQL safe; use parameters

⏱ 30-second answer

A raw string literal (C# 11) starts and ends with at least three double quotes and contains text exactly as written: no escape sequences, so backslashes and quotes are literal. In a multi-line raw string the opening quotes end their line, the closing quotes sit on their own line, and the whitespace before the closing quotes is removed from every line, so you can indent the literal with your code. To include """ in the text, use more quotes for the delimiters. For interpolation, the number of $ signs is the number of braces that start a hole: $$"""{ "id": {{id}} }""" keeps single braces literal, which is perfect for JSON. Add u8 for a UTF-8 ReadOnlySpan<byte>. They make JSON, SQL, regex and test fixtures readable.

🗣 Full answer (senior)

How I would answer this in an interview

Raw string literals (C# 11) let you write multi-line text containing quotes, backslashes and braces without escaping, delimited by three or more double quotes. They're ideal for JSON, SQL, XML, regex and test fixtures. I'd explain the syntax, indentation removal, quotes inside the text, interpolated raw strings with multiple $, UTF-8 raw strings, and real uses.

1. Syntax

string single = """He said "hello" and left C:\temp""";   // single-line raw string

string json = """
    {
      "orderId": 42,
      "status": "paid",
      "path": "C:\\exports"
    }
    """;
  • Start with at least """. Single-line raw strings put content on the same line; multi-line raw strings start content on the next line after the opening delimiter.
  • No escape sequences are processed: \n, \\, \" are literal characters. (In the JSON above, C:\\exports stays exactly as written — which is valid JSON escaping, because the string is passed through untouched.)

2. How indentation is removed

In a multi-line raw string, the closing delimiter's column defines the indentation to strip:

        var sql = """
            SELECT Id, Total
            FROM Orders
            WHERE CustomerId = @customerId
            """;
  • The whitespace before the closing """ (12 spaces here) is removed from every content line.
  • Every content line must start with at least that whitespace (otherwise CS9003).
  • The first newline (after the opening delimiter) and the last newline (before the closing delimiter) are not part of the value.
  • Mixed tabs/spaces must match exactly.

So you can indent raw strings naturally with your code without the indentation leaking into the value.

3. Quotes inside the text

If the content contains """, use more quotes for the delimiters:

var doc = """"
    A raw string with """ inside it.
    """";

The delimiter must be longer than any run of quotes in the content.

4. Interpolated raw strings with multiple $

The number of $ signs sets how many braces open an interpolation hole, so single braces stay literal — perfect for JSON and C#-like templates:

var payload = $$"""
    {
      "orderId": {{order.Id}},
      "total": {{order.Total.ToString(CultureInfo.InvariantCulture)}},
      "tags": ["priority", "au"]
    }
    """;
  • With $$, holes are {{expr}}; a single { or } is literal.
  • With $$$, holes are {{{expr}}}, letting {{ appear literally (e.g. Mustache/Handlebars templates).
  • Interpolation follows normal rules (current culture, format specifiers like {{total:C}}).

5. UTF-8 raw strings

Append u8 to get a ReadOnlySpan<byte> encoded at compile time:

ReadOnlySpan<byte> body = """
    {"status":"ok"}
    """u8;
await response.Body.WriteAsync(body.ToArray(), ct);   // or write the span directly with PipeWriter

No runtime encoding or allocation for constant payloads. (Interpolation isn't supported with u8.)

6. Real uses

  • Test fixtures: JSON request/response bodies, expected API output, snapshot data — readable and copy-pasteable from tools.
  • SQL: Dapper queries, FromSql templates (still use parameters, not interpolation of values!), migration scripts in code.
  • Regex: patterns with backslashes and quotes without double-escaping (better still with [GeneratedRegex]).
  • Code generation: source generators emitting C# with braces ($$""").
  • Prompts/templates: LLM prompt templates, email templates with placeholders.
  • XML/HTML snippets in tests.

Cautions: don't build SQL/JSON with untrusted values via interpolation — use parameters and serialisers; raw strings make injection easier to write, not safer.

7. A production example

Integration tests for a payments webhook contained JSON with escaped quotes and \ns across 60 lines — nobody could read or update them, and a missing escape made one test silently send invalid JSON. Converting fixtures to raw string literals made them identical to the provider's documentation samples; we used $$""" for the few dynamic fields. Fixture-related test failures disappeared and reviewing payload changes became trivial.

🔍 What the interviewer is checking

The interviewer checks that you know modern string syntax and use it safely.

Green flags

  • Explains delimiter, indentation and multiple-dollar rules correctly.
  • Uses $$ for JSON.
  • Mentions UTF-8 literals and StringSyntax.
  • Notes that raw strings do not prevent injection.

Red flags

  • Confuses raw strings with verbatim strings.
  • Thinks escape sequences still work.
  • Builds SQL with interpolated user values.

📚 Learn in depth

1. Rules in detail

  • Opening delimiter: N ≥ 3 quotes. Closing delimiter: exactly N quotes.
  • Single-line form: content and delimiters on one line; content can't start or end with a quote (use multi-line form or more quotes).
  • Multi-line form: opening delimiter must be followed by a newline (only whitespace allowed after it); closing delimiter must be on its own line (preceded only by whitespace).
  • Whitespace before the closing delimiter is removed from each line; blank lines are allowed even if shorter.
  • No escapes are processed at all.

2. Interpolation brace counting

For $ repeated M times:

  • An interpolation hole is opened by exactly M {.
  • A run of fewer than M braces is literal content.
  • A run of more than 2M-1 braces is an error (ambiguous); you can't have literal brace runs as long as the hole delimiter + content braces in some combinations — choose M larger than the longest literal brace run.

3. Comparison with verbatim strings

Regular "..." Verbatim @"..." Raw """..."""
Escapes Processed Only "" for a quote None
Multi-line No (use \n) Yes, indentation kept Yes, indentation stripped
Quotes \" "" As-is (with longer delimiters)
Braces in interpolation {{ {{ Literal with $$

4. Constant raw strings

Raw strings without interpolation are compile-time constants: const string Query = """..."""; works and can be used in attributes (e.g. [GeneratedRegex("""...""")]).

5. Line endings

The line endings inside a raw string are those of the source file (CRLF on Windows checkouts, LF elsewhere). With Git's core.autocrlf, the same code can produce different string values on different machines — a source of flaky snapshot tests. Normalise with .gitattributes (*.cs text eol=lf) or ReplaceLineEndings() when comparing.

Try it yourself

  1. Write a multi-line raw string with misaligned indentation and read CS9003.
  2. Embed """ inside content using four-quote delimiters.
  3. Build a JSON template with $$""" and a format specifier.
  4. Compare raw string values on Windows and Linux checkouts with different line ending settings.

💻 Code

using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

public static class Program
{
    public static void Main()
    {
        int id = 42;
        string name = "Mia";

        // $$: single braces are literal JSON, {{ }} are holes
        string json = $$"""
            {
              "id": {{id}},
              "name": "{{name}}",
              "tags": ["vip", "nsw"]
            }
            """;
        Console.WriteLine(json);
        Console.WriteLine(JsonDocument.Parse(json).RootElement.GetProperty("id").GetInt32());

        // Readable SQL; values still go in as parameters, never interpolated
        const string sql = """
            SELECT o.Id, o.Total
            FROM   dbo.Orders AS o
            WHERE  o.CustomerId = @customerId
              AND  o.Status <> 'Cancelled'
            ORDER BY o.CreatedUtc DESC;
            """;
        Console.WriteLine(sql);

        // Regex without doubled backslashes
        var abn = new Regex("""^\d{2}\s?\d{3}\s?\d{3}\s?\d{3}$""");
        Console.WriteLine(abn.IsMatch("51 824 753 556"));

        // Text containing three quotes: use four-quote delimiters
        string doc = """"
            Use """ to start a raw string.
            """";
        Console.WriteLine(doc);

        // UTF-8 bytes encoded at compile time
        ReadOnlySpan<byte> body = """{"ping":true}"""u8;
        Console.WriteLine(Encoding.UTF8.GetString(body));

        Validate("""{"ok":true}""");
    }

    // IDE highlights and checks JSON passed to this parameter
    static void Validate([StringSyntax(StringSyntaxAttribute.Json)] string payload)
        => Console.WriteLine(JsonDocument.Parse(payload).RootElement.GetProperty("ok"));
}

⚠ Common mistakes

1. Interpolating untrusted values into SQL

What happens: $"""SELECT ... WHERE Name = '{name}'""" passed to raw SQL APIs. Why it is wrong: SQL injection. Do instead: parameters (FromSql with FormattableString, Dapper parameters).

2. Hand-building JSON with values

What happens: $$"""{"name":"{{name}}"}""" with user input. Why it is wrong: quotes/backslashes in values break or inject JSON. Do instead: use System.Text.Json for dynamic data; raw strings for fixtures.

3. Misaligned closing delimiter

What happens: closing """ indented more than content lines. Why it is wrong: compile error CS9003. Do instead: align the closing delimiter with the content's left edge.

4. Expecting escape sequences

What happens: writing \n expecting a newline. Why it is wrong: raw strings don't process escapes. Do instead: use actual line breaks.

5. Too few $ for brace-heavy content

What happens: $"""{ "a": {x} }""". Why it is wrong: literal braces are treated as holes. Do instead: use $$ (or more).

6. Line-ending dependent tests

What happens: snapshot comparisons fail across OSes. Why it is wrong: raw strings inherit source line endings. Do instead: normalise line endings.

7. Culture issues in interpolated holes

What happens: {{total}} formats with current culture. Why it is wrong: decimal separators differ across environments. Do instead: format with invariant culture for machine data.

8. Overusing raw strings for long templates in code

What happens: large email/HTML templates embedded in C#. Why it is wrong: hard for non-developers to edit; redeploy for text changes. Do instead: template files or a templating engine.

🏛 Senior insight: trade-offs

Raw string literals are a small feature with a large effect on test readability and review quality. When a JSON contract or SQL query is written exactly as it is sent, reviewers can compare it with documentation or a query plan without decoding escapes.

Useful team conventions:

Practice Why
$$""" for JSON templates consistent, no brace doubling
[StringSyntax] on APIs taking JSON, regex or URIs IDE validation and highlighting
SQL as const raw strings plus parameters readable and safe
Snapshot tests with raw strings or verified files clear diffs

They also matter in source generators, where templates written as raw strings look like the generated code, making generators easier to maintain.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate mentions $ and holes. Why: Checks normal string interpolation first.
    How to answer

    $"Hello {name}" is compiled, not parsed at run time. With a string target, C# 10+ uses DefaultInterpolatedStringHandler, which formats each hole straight into a pooled buffer without boxing, then allocates the final string once. Holes take alignment and format ({total,10:C}), and formatting uses the current culture, so for machine-readable text I use string.Create(CultureInfo.InvariantCulture, $"..."). Assigned to FormattableString, it keeps the format and arguments separate, which is how EF Core's FromSql($"...") turns holes into SQL parameters. Raw $$""" strings help with JSON. Two traps: interpolating into raw SQL causes SQL injection, and interpolating into log templates destroys structured logging.

  2. When: Candidate mentions the u8 suffix. Why: UTF-8 literals connect to encoding.
    How to answer

    In memory a .NET string is UTF-16; an Encoding converts it to and from bytes at the boundaries (files, HTTP, databases). I default to UTF-8 without a BOM (new UTF8Encoding(false)), and I use throwOnInvalidBytes: true when bad input should fail rather than silently become �. Legacy formats like Windows-1252 need CodePagesEncodingProvider registered. For speed there are "OK"u8 literals and the Utf8 class for span transcoding. For text correctness, Rune handles code points beyond the BMP, StringInfo counts grapheme clusters (what users see), and I normalise to NFC before comparing or storing user input.

More in Modern C#

Last updated 30 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me