skip to content

In Flutter, how do ConstrainedBox, UnconstrainedBox and LimitedBox differ in what they do to the constraints their child receives?

level: middleimportance: nice to knowfreq 30%

answer

  1. enforce() inside the parent's range
  2. BoxConstraints() to the child
  3. constrainedAxis keeps one axis
  4. limit only when unbounded
  5. Container with no child collapses

basics

~20 s

ConstrainedBox adds limits clamped inside the parent's range; UnconstrainedBox removes the parent's constraints so the child takes its natural size, overflowing with a debug warning if too big; LimitedBox caps an axis only when the incoming maximum is infinite.

solid answer

~40 s

`ConstrainedBox` applies `additionalConstraints.enforce(incoming)`: it can narrow the parent's range, never widen it. `UnconstrainedBox` gives its child `BoxConstraints()` — zero to infinity — so the child lays out at its natural size, then sizes itself within its own constraints and aligns the child (default `Alignment.center`). `constrainedAxis` keeps one axis's constraints. If the child is bigger, you get the debug stripes and message, and with the default `clipBehavior: Clip.none` nothing is clipped. `LimitedBox` passes constraints through unchanged unless an axis is unbounded; only then does its `maxWidth`/`maxHeight` (default infinity) become the maximum. It exists for widgets that fill their parent but also live in unbounded places such as a `Row` or a list.

go deeper

for a junior

Recall that ConstrainedBox adds limits, UnconstrainedBox removes them and LimitedBox only matters when a parent gives infinite space.

for a middle

Explain the exact transformation each one applies to the incoming constraints, including enforce() clamping and LimitedBox's unbounded check.

for a senior

Pick the right one for a reusable component that must work in bounded and unbounded parents, and explain why an empty Container vanishes in a Row.

for a principal

Guide a design-system team on which sizing primitives shared components may rely on, so they behave predictably in every parent context.

## Three boxes, three transformations All three widgets sit between a parent and a child and change the `BoxConstraints` the child receives. They are easy to confuse because their names all mention constraints, but each performs a different transformation. | Widget | Child receives | When it has an effect | Typical use | |---|---|---|---| | `ConstrainedBox` | its `constraints` clamped into the parent's range | always, within the parent's limits | a minimum button width, a maximum card width | | `UnconstrainedBox` | `BoxConstraints()` (0 to infinity), or one axis kept via `constrainedAxis` | always | let a child take its natural size, e.g. inside a tight parent | | `LimitedBox` | incoming constraints, with a finite max substituted on an unbounded axis | only when the incoming max is infinite | a fill-the-parent widget that also appears in a `Row` or list | ## ConstrainedBox: narrowing only `ConstrainedBox(constraints: ...)` builds a `RenderConstrainedBox`, the same render object `SizedBox` uses. It lays out its child with `additionalConstraints.enforce(constraints)`, clamping each of the four values into the parent's range. - Under a loose parent of 0-360 wide, `BoxConstraints(minWidth: 120, maxWidth: 240)` gives the child 120-240. - Under a tight parent of exactly 360, the same constraints become 360-360; the box cannot make the child narrower. - Under a parent with max 100, `minWidth: 120` clamps to 100. The rule to state: **a ConstrainedBox can add limits but cannot remove any the parent imposed**. ## UnconstrainedBox: removing the parent's limits `UnconstrainedBox` is a `ConstraintsTransformBox` configured to pass `BoxConstraints()` to its child. Its parameters are: - `alignment` — defaults to `Alignment.center`; used when the child and the box differ in size. - `constrainedAxis` — `null` by default (both axes free). `Axis.horizontal` keeps the incoming width constraints and frees the height; `Axis.vertical` keeps height and frees width. - `clipBehavior` — defaults to `Clip.none`. - `textDirection` — needed only for a directional alignment. Layout then goes: the child lays out at its natural size; the `UnconstrainedBox` tries to adopt that size **within its own constraints**; if the child is larger, the box has overflowed. In debug mode that paints the yellow-and-black stripes and prints an overflow message. With `clipBehavior` set to anything but `Clip.none`, the excess is clipped instead of drawn outside, and the stripes are not shown. Two cautions: 1. A child that tries to be infinitely big, such as `Container(width: double.infinity)`, fails under `UnconstrainedBox` with 'BoxConstraints forces an infinite width.' 2. `OverflowBox` is the related widget that lets a child exceed its parent without any warning; `UnconstrainedBox` deliberately warns. ## LimitedBox: a limit that applies only to infinity `LimitedBox(maxWidth: ..., maxHeight: ...)` looks like a `ConstrainedBox` with maximums, but its render object checks the incoming constraints first: - If the incoming width is bounded, it passes the incoming width constraints unchanged and **ignores** its own `maxWidth`. - If the incoming width is unbounded, it uses its `maxWidth` as the maximum. - Both limits default to `double.infinity`, so a bare `LimitedBox` does nothing. The Flutter docs' demonstration: `UnconstrainedBox(child: LimitedBox(maxWidth: 100, child: Container(width: double.infinity, height: 100)))` renders a 100-pixel box, because `LimitedBox` sees an infinite width and substitutes 100. Swap the `UnconstrainedBox` for `Center` and the limit disappears: `Center` passes a bounded width, so `LimitedBox` stays out of the way and the container fills the screen width. The framework uses the same trick internally. A `Container` with no child and no tight constraints wraps `ConstrainedBox(constraints: BoxConstraints.expand())` in `LimitedBox(maxWidth: 0, maxHeight: 0)`. In a bounded parent the empty container expands to fill it; in an unbounded axis it collapses to zero rather than throwing. ## Choosing between them - Need a minimum or maximum that the parent's range can accommodate? `ConstrainedBox`. - Need the child's natural size regardless of a tight parent, and want to be warned if it doesn't fit? `UnconstrainedBox`. - Writing a reusable widget that fills its parent but must survive an unbounded parent? `LimitedBox` inside it.

  • In Flutter, how does UnconstrainedBox differ from Center when both wrap a child inside a tight parent?
    `Center` loosens: minimums drop to zero but maximums stay, so the child can be smaller than the parent, never bigger. `UnconstrainedBox` removes the maximums as well, so the child can be larger than the parent. It then overflows, with debug stripes and a console message, clipped only if you set a `clipBehavior` other than `Clip.none`.
  • Why does an empty Container disappear inside a Row but fill a Column's width?
    A childless `Container` without tight constraints is built as `LimitedBox(maxWidth: 0, maxHeight: 0)` around an expanding `ConstrainedBox`. In a `Row` its width is unbounded, so the limit of 0 applies and it collapses. In a `Column` its width is bounded, so the limit is ignored and it expands to the maximum.

saying these in an interview costs you the question

  • LimitedBox always caps its child at maxWidth and maxHeight.
  • ConstrainedBox can make a child smaller than a tight parent requires.
  • UnconstrainedBox clips an oversized child by default.
  • UnconstrainedBox makes infinite-width children render safely.
  • LimitedBox and ConstrainedBox with a maxWidth behave identically.