In Flutter, what is the difference between Expanded and Flexible inside a Row or Column, and what does FlexFit change?
answer
- inflexible children sized first
- flex factor defaults to 1
- Expanded means FlexFit.tight
- Flexible defaults to FlexFit.loose
- unused share is not redistributed
basics
~20 sExpanded is Flexible with FlexFit.tight: the child must fill its share of the leftover main-axis space. Flexible defaults to FlexFit.loose: the child may be smaller than its share, and the unused space stays free instead of going to siblings.
solid answer
~40 sA `Row` or `Column` first lays out inflexible children with an unbounded main axis, subtracts their sizes (and any `spacing`) from its main-axis maximum, then splits what remains among flexible children in proportion to their `flex` factors, which default to 1. `FlexFit` decides how a child uses its share. `Expanded` is `Flexible` with `fit: FlexFit.tight`: min and max both equal the share, so the child fills it exactly. Plain `Flexible` defaults to `FlexFit.loose`: min 0, max the share, so the child can be smaller, such as a short `Text`. Its unused space is not handed to siblings; it becomes free space that `mainAxisAlignment` places. On a nutrition-label row I'd give the bar `Expanded` so it always spans, and the name `Flexible` so a short name doesn't reserve empty width.
code
dart · 24 linesimport 'package:flutter/material.dart';
class NutrientRow extends StatelessWidget {
const NutrientRow({super.key, required this.name, required this.percent, required this.amount});
final String name;
final double percent; // 0.0 - 1.0 of daily value
final String amount;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 2,
child: Text(name, maxLines: 1, overflow: TextOverflow.ellipsis),
),
Expanded(flex: 3, child: LinearProgressIndicator(value: percent)),
const SizedBox(width: 8),
Text(amount), // inflexible: always shown in full
],
);
}
}go deeper
Recall that Expanded fills the remaining space and that the flex factor, default 1, sets the proportions.
Walk through the algorithm: inflexible children first, free space divided by total flex, tight versus loose minimums, and why unused loose space is not redistributed.
Choose tight or loose per child from the design intent, such as aligned bars across rows versus text that should hug its content, and diagnose ParentDataWidget misuse.
Set component guidelines on when list rows use fixed columns versus flexible shares, balancing visual alignment across rows against content-driven sizing.
## Flexible children in a flex layout `Row`, `Column` and `Flex` distinguish two kinds of children: - **Inflexible** children — anything not wrapped in `Flexible`, `Expanded` or `Spacer`. They are measured at their natural size. - **Flexible** children — wrapped in `Flexible` or its subclass `Expanded`, carrying a `flex` factor greater than zero. They share whatever main-axis space is left. `Flexible` and `Expanded` are not layout widgets themselves: they are `ParentDataWidget`s that write a `flex` factor and a `fit` onto their child's parent data for the enclosing `Flex` to read. That is why they must be direct children of a `Row`, `Column` or `Flex`; anywhere else Flutter reports 'Incorrect use of ParentDataWidget.' ## The algorithm in order The `RenderFlex` documentation describes layout in six steps. The ones that matter here: 1. **Lay out inflexible children** with an unbounded main axis and the incoming cross-axis constraints. Add up their main-axis sizes, plus `spacing` between children. 2. **Compute the free space**: the main-axis maximum minus that total, floored at zero. 3. **Divide it by the total flex**: each flexible child's share is `freeSpace / totalFlex * flex`. 4. **Lay out each flexible child** with its share as the main-axis maximum. The fit decides the minimum. 5. **Size the Row or Column** according to `mainAxisSize`, then **position children** with `mainAxisAlignment` and `crossAxisAlignment`. Worked example: a 300-pixel `Row` with a fixed 60-pixel icon and two `Expanded` children with flex 1 and 2. Free space is 240; per unit of flex that is 80; the children get 80 and 160. ## FlexFit: tight versus loose | | `Expanded` | `Flexible` (default) | |---|---|---| | `fit` | `FlexFit.tight` (fixed) | `FlexFit.loose` (can be set to tight) | | Child's main-axis min | its full share | 0 | | Child's main-axis max | its full share | its full share | | Child smaller than share? | not allowed | allowed | | `flex` default | 1 | 1 | `Expanded(child: x)` is exactly `Flexible(fit: FlexFit.tight, child: x)`. The difference shows only when the child's natural size is smaller than its share: - Under `Expanded`, a short `Text` is forced to the full share (its box is wide, its glyphs sit at the start). - Under `Flexible`, the `Text` takes only the width it needs. The unused part of a loose child's share is **not redistributed** to its siblings. The shares are computed once, before any flexible child is laid out. The leftover becomes free space, and `mainAxisAlignment` decides where it goes. ## Choosing on a nutrition-label row Each row of the label shows a nutrient name, a bar for the percentage of daily value and the amount: - **Amount** (`'4.5 g'`): inflexible. It should always show in full. - **Bar**: `Expanded(flex: 3)`. A bar has no natural width, so it must be told to fill its share, and a tight fit keeps all rows' bars starting and ending consistently. - **Name**: `Expanded(flex: 2)` if the names should form a column of equal width across rows; `Flexible` if a short name should not reserve empty width. With either, a long name now has a bounded width, so `maxLines: 1` and `TextOverflow.ellipsis` can truncate it instead of overflowing. ## Spacer and flex factors `Spacer` is shorthand for an `Expanded` with an empty child: `Spacer(flex: 2)` takes two units of free space and pushes its neighbours apart. Flex factors are integers, and a `flex` of 0 or `null` makes a child inflexible. ## Mistakes interviewers look for - Wrapping a widget in `Expanded` outside a `Flex` (inside `Padding`, `Stack` or `Wrap`). - Expecting `Flexible`'s unused space to go to another `Flexible` sibling. - Using `Expanded` in a parent with an unbounded main axis, which has no finite share to fill. - Believing `Expanded` 'makes a widget as big as possible' in general: it fills only the leftover share along the main axis.
- In Flutter, what happens if you put Expanded directly inside a Padding that sits in a Row?`Expanded` is a `ParentDataWidget` that must be a direct child of a `Flex`. With a `Padding` in between, its parent data cannot be applied and Flutter reports 'Incorrect use of ParentDataWidget.' Swap the nesting: `Expanded(child: Padding(...))`, so the `Expanded` is the Row's direct child.
- Can you make Flexible behave exactly like Expanded?Yes. `Expanded` is a `Flexible` whose `fit` is fixed to `FlexFit.tight`, so `Flexible(fit: FlexFit.tight, child: x)` lays out identically to `Expanded(child: x)`. `Expanded` exists because the tight fit is the common case and reads more clearly.
It is like splitting a restaurant bill: fixed-price dishes are paid first, then the remainder is divided by shares. An Expanded diner must spend their whole share; a Flexible diner may spend less, and their change stays on the table rather than going to the others.
saying these in an interview costs you the question
- Expanded and Flexible are identical apart from the name.
- A Flexible child's unused share goes to its flexible siblings.
- Expanded makes a widget as big as the whole screen.
- Flex space is shared before inflexible children are measured.
- Expanded works when wrapped inside Padding or Container within a Row.