stopSequences in the Gemini API and Its Limits
7 min read · updated August 11, 2026
stopSequences is a string match applied to the output as it is produced. When one matches, generation halts, the matched text is removed, and the candidate comes back with a finish reason that is indistinguishable from a natural ending. All three of those facts have consequences.
What it does
It lives in generationConfig and takes an array of strings. Google documents it in the generateContent API reference as the set of character sequences that will stop output generation, with the API stopping at the first appearance of any of them:
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{"role": "user", "parts": [{
"text": "List three colours, one per line, then write END."
}]}],
"generationConfig": {
"stopSequences": ["END"],
"maxOutputTokens": 64
}
}'The documented response for that request stops before the marker:
{
"candidates": [{
"content": {"role": "model", "parts": [{"text": "Red\nBlue\nGreen\n"}]},
"finishReason": "STOP",
"index": 0
}],
"usageMetadata": {"promptTokenCount": 18, "candidatesTokenCount": 9, "totalTokenCount": 27}
}This is a post-processing filter on the decoded text, not a change to the model. The model was going to write END; the service cut the stream when it saw it. Nothing about the model’s behaviour before that point is altered by the presence of the parameter.
How many you can pass
The array has a documented maximum size, and exceeding it is an INVALID_ARGUMENT error on the request rather than a silent truncation of the list. At the time of writing Google documents a cap of five sequences for the Gemini API.
GenerationConfig reference for the surface you call before designing around a number. If you find yourself wanting more than a handful, see the last section — the parameter is probably not the right tool for what you are doing.Every entry costs something at generation time, because the service must scan the output for each of them. The cap is low for a reason, and the reason is not arbitrary strictness.
The stop text is removed
The matched sequence does not appear in the returned text. This is worth stating explicitly because the two obvious designs behave differently:
- If you are using a delimiter as a terminator — stop when you see
</answer>— the removal is what you want, and your parser gets clean text. - If you need the delimiter present, because a downstream parser expects a well-formed document, you have to append it yourself. The model wrote it; the API removed it; nobody will put it back.
The second implication is about billing and truncation detection: the tokens of the stop sequence were generated and are counted in candidatesTokenCount even though they are not in the text you received. A response whose text is shorter than the reported token count is not a bug.
The third is the awkward one. finishReason is STOP for both a natural ending and a stop-sequence match. There is no separate value that tells you a sequence fired. If you must distinguish them — to know whether the model completed its thought or was cut off — you have to design the prompt so the two cases are distinguishable in the content itself, for instance by requiring a closing marker that only appears on a complete answer.
Why a sequence sometimes does not fire
The failure people report is a stop sequence being ignored. The mechanism is tokenization. The model emits tokens, not characters, and the matcher works over the decoded text of the stream — so a sequence that never appears as a contiguous decoded substring never matches.
Two concrete cases:
- Whitespace you did not account for. A stop sequence of
"\n\n"will not match if the model wrote"\n \n". Exact string matching means exact. - The model wrote something similar but not identical.
"END"does not match"end"or"**END**". Instructing the model in the prompt to emit the exact marker is not optional — the parameter does not make it happen, it only reacts when it does.
The fix in both cases is the same: state the terminator in the prompt in exactly the form you are matching, and make it something the model is unlikely to produce a variant of. A distinctive multi-character marker like <<<DONE>>> matches more reliably than a common word.
A related caution for streaming: with streamGenerateContent the stop sequence is applied server-side before chunks are sent, so you will not see the matched text arrive and then disappear. But chunk boundaries are not word boundaries, so any matching you do yourself on the client must buffer across chunks.
That last point deserves a sentence more, because it is the reason client-side stop matching is harder than it looks. A three-character marker can arrive split across two chunks — EN at the end of one and D at the start of the next — so a matcher that tests each chunk in isolation misses it. If you are implementing your own termination on top of a stream, keep a rolling buffer of at least the length of your longest sequence and match across the join.
Stop sequences and structured output
Combining stopSequences with responseMimeType: “application/json” is where this parameter does real damage, and the reason is that the two features operate at different layers.
Constrained decoding guarantees the shape of what the model produces by masking the sampler. A stop sequence is applied to the decoded output afterwards. So the constraint can be perfectly satisfied and the text you receive can still be invalid JSON, because the stop sequence cut it off before the closing brace. You get a parse error on a response the API considers successful, with finishReason: STOP and no indication anything was trimmed.
The trap is a stop sequence chosen for one purpose that also occurs inside legitimate data. A sequence of "\n\n" set to stop a rambling preamble will also fire inside any string field containing a paragraph break. The general rule: if you are using a response schema, you do not need stop sequences and should not set them — the schema already bounds the output.
Function calling has a related asymmetry. Stop sequences are matched against generated text, and a functionCall part is structured data rather than text, so a stop sequence will not truncate a tool call the way it truncates prose. That is usually what you want, but it does mean a stop sequence you added as a safety net against runaway output provides no protection at all on a turn that calls a tool.
What it is actually good for
- Bounding a completion inside a larger format. Generating one field of a template and stopping before the model invents the next section.
- Preventing role-play continuation. A stop sequence of
"\nUser:"stops a model that has started writing both sides of a conversation. - Cutting a preamble off a structured answer. Though for JSON specifically, use a response schema instead — constrained decoding guarantees the shape, where a stop sequence only trims what came out.
- Not for enforcing length. That is
maxOutputTokens, which producesMAX_TOKENSand is deterministic. A stop sequence fires only if the model happens to write the string.