Use a question group when related answers belong under one heading. An ordinary group combines independent questions; a repeated group applies the same questions to several named rows. Each child keeps its normal options, defaults, recodes, tags, validation and reporting identity.
Ordinary question groups
Wrap normal question calls in s.question_group:
from survey import Survey
s = Survey(**globals())
with s.question_group(question="About you"):
age = s.numeric_question("Your age", min_max=(0, 120))
brand = s.select_question("Your preferred brand", options=["Brand A", "Brand B"])
s.store_value("Age next year", age + 1)
The answers are ordinary response values, so age behaves like the numeric answer and brand like the selected answer. Use them after the group completes.

Ordinary groups support:
| Control | Survey method |
|---|---|
| Text, including multiple responses | text_question |
| Number | numeric_question |
| Single choice | select_question |
| Multiple choice | multi_select_question |
| Rating | rating_question |
| Net promoter score | net_promoter_score_question |
| Ranking | ranking_question |
| Text highlighting | text_highlighter_question |
Repeated question groups
Use s.grid_question_group for a set of mixed questions repeated across rows:
with s.grid_question_group(
question="About your children",
rows=["Child 1", "Child 2"],
row_name="child",
):
ages = s.numeric_question("Age of {child}", min_max=(0, 17))
interests = s.multi_select_question(
"Interests of {child}", options=["Music", "Sport", "Reading"]
)
s.store_value("First child age", ages["Child 1"])
Each assigned answer is a dictionary keyed by the original row strings. For example, ages["Child 1"] contains that child's numeric answer. row_name supplies the row value for text piping, as in {child}, and for reporting tags.
Repeated groups support only single-response text, numeric, single-select and multi-select questions. Rating, NPS, ranking, text highlighting and multi-response text are supported in ordinary groups only.

Supply a non-empty list of unique, non-empty row strings. Choose a row_name that is a valid identifier and does not conflict with a reserved survey topic, id or another tag.
Landscape and portrait presentation
Landscape forms show repeated controls together, with each row's questions side by side. In portrait, respondents answer each child as a normal question in sequence, with its row context retained. Ordinary groups keep related controls under one heading in landscape. Preview both desktop and mobile before fielding.
The row and child-question identities stay the same across layouts. See Traditional or Chat based survey experience for choosing the respondent experience.
Validation and retained answers
Each question retains its own validation. A rejected answer stops progression at that child, and the respondent must correct it before the group finishes. Accepted child answers are saved as the respondent progresses; reloading restores those accepted values and resumes with the first pending child. Values that have not been submitted remain drafts.
Prepare values used by the group before entering its block. The block supports question calls, answer assignments and nested s.tag contexts. Read assigned answers only after the block completes, including answers needed by custom validators. Branching, calculations, nested question groups and multi-step flows such as interviews or OTP verification are not supported inside a group.
Reporting and identifiers
The heading does not create a reportable answer. Child questions retain their own reporting variables and optional IDs; repeated answers retain the row context. A heading ID does not replace child IDs, and normal question-identity uniqueness rules still apply.
See question_group and grid_question_group in the API reference for signatures and parameters.
