Representative interview topic

Coding interview: How would you build a safe Python 3.14 t-string processor?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

Your team is upgrading to Python 3.14 and wants to inspect interpolations before joining text. Explain what a t-string produces, how to traverse it, and why the template API is not automatic escaping.

Prompt and context

Your team is upgrading to Python 3.14 and wants to inspect interpolations before joining text. Explain what a t-string produces, how to traverse it, and why the template API is not automatic escaping.

PEP 750 adds template strings to Python 3.14. t"..." does not directly produce str; it produces string.templatelib.Template, retaining static segments and interpolation objects so an application can validate, escape, or transform each value before joining.

What the interviewer is testing

Cover the difference between f-string immediate stringification and a structured t-string, the fields of Template and Interpolation, raw values and format specs, context-specific escaping, side-effect control, and compatibility with older Python versions.

30-second answer framework

“I would start by saying that a t-string is not safe output by itself. It preserves static segments and interpolation objects so a renderer can inspect the raw value, expression text, conversion, and format spec, then apply SQL-, HTML-, or logging-specific encoding. I would restrict value types, test nested templates, format specs, exceptions, and old-version fallback, and validate the final output again at its destination.”

Step-by-step deep dive

Step 1: Contrast f-strings and t-strings

An f-string evaluates and formats to str, so its intermediate structure is gone. A t-string uses the t prefix and produces a Template; static text and interpolations remain separate until a renderer joins them.

Step 2: Understand the core objects

A template contains string segments and Interpolation objects. An interpolation records its value, expression text, conversion marker, and format spec, allowing policy to consider source and destination context. Iteration does not mean the result is already safe.

Step 3: Write a minimal processor

The following processor only demonstrates structural traversal; a real system still needs context-specific encoding:

python
from string.templatelib import Template

def render_plain(template: Template) -> str:
    parts = []
    for item in template:
        if isinstance(item, str):
            parts.append(item)
        else:
            parts.append(str(item.value))
    return "".join(parts)

message = t"Hello, {name}!"

str(item.value) is suitable only for a plain-text example. SQL, HTML, Shell, and logs need their own parameterization or encoding APIs.

Step 4: Handle conversions and format specs

An interpolation may carry !s, !r, or a format spec. The renderer should decide whether conversion is allowed, then encode the converted value for the destination context. repr is not HTML escaping, and a format spec must not bypass a type allowlist.

Step 5: Avoid side-effect expressions

A t-string still evaluates interpolation expressions to create Interpolation objects. The processor is not a sandbox; an untrusted template is already risky at construction time. For plugins or user input, accept data placeholders rather than dynamically executed expressions.

Step 6: Choose policy by output context

Plain text can use an allowlisted conversion; HTML needs an audited HTML escaper; SQL needs driver parameter binding; Shell should avoid concatenation and use an argument array; structured logs should carry fields instead of one joined line. Template supplies structure, not these security boundaries.

Step 7: Support versions and discover the API

Python 3.14 provides string.templatelib. A compatibility layer should select an existing template library or explicitly fall back to f-strings on older versions; it must not pretend to preserve interpolation structure there. Check the runtime version at startup and run CI on every supported interpreter.

Step 8: Test and observe

Test static segments, nested t-strings, format specs, exceptions, __format__ side effects, and large templates. Record rejected value types and template sources without logging sensitive interpolation content; run context-specific security regressions on the output.

Trade-offs and boundaries

Structured processing versus simple joining

Structure helps when a boundary needs auditing, localization, or multiple output targets. A one-off plain-text script is simpler with an f-string. Choose by risk and reuse rather than putting every string through a template pipeline.

Flexible formatting versus predictable types

Allowing arbitrary __format__ is expressive but introduces side effects and type drift. A shared renderer should restrict types, conversions, and format specs and return diagnosable errors for rejected values.

Template object versus final string

A Template is useful inside a controlled boundary. The final output still needs destination-specific validation before sending, storing, or executing it; it is not an already encoded security token.

Failure drills and evolution plan

HTML escaping is bypassed

Give an interpolation a custom __str__ returning markup and verify that plain str conversion is not safe. Replace it with context encoding and add malicious-value regressions.

A format spec raises

Use an unsupported spec and an object whose __format__ raises. The processor should identify the interpolation and reject the render instead of returning partial output.

An old interpreter runs the module

Start the same module on Python 3.13. Version checking should produce a clear error or choose an explicit compatibility implementation rather than deferring a syntax failure to production.

Common mistakes and follow-ups

Mistake 1: Assuming t-strings escape automatically

Follow-up: What do they solve? They preserve structure so policy can run before joining; SQL and HTML safety still belongs to dedicated APIs.

Mistake 2: Assuming interpolation expressions never run

Follow-up: When are they evaluated? They run while constructing the t-string, so an untrusted template does not gain sandboxing from this syntax.

Mistake 3: Treating repr as safe encoding

Follow-up: Why not? repr is a debugging representation and does not guarantee HTML, SQL, Shell, or log-field encoding rules.

Extended follow-ups and model answers

Should t-strings replace every f-string?

No. The structure is useful only at boundaries that inspect or transform interpolations before joining; ordinary plain text can remain an f-string.

How would you build a SQL renderer?

Do not generate a SQL string. Map static segments and values to driver placeholders, bind values through the driver, and use the template only to describe the statement structure.

How do you support older Python versions?

Check the runtime, use string.templatelib on 3.14, and use an existing implementation or an explicit unsupported error on older versions. Verify semantic differences with the same security test suite.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool