Explain the difference between static, relative, absolute, fixed, and sticky positioning

Beginner10 min interview
Skills tested:
Stating which values remove an element from normal flowIdentifying the containing block for each valueKnowing that relative offsets do not affect surrounding layoutExplaining what position: relative enables even without offsetsChoosing the right value for overlays, badges, headers, and sidebars

Advertisement

🧩 Scenario

Positioning is the first thing you reach for when an element must sit somewhere the normal flow will not put it: a notification badge on an avatar, a close button in a modal corner, a header that follows the scroll. Choosing wrong produces layouts that look right until the content length, viewport size, or an ancestor transform changes, at which point the element anchors to something you did not intend.

Architecture Walkthrough

In Flow Versus Out of Flow

The first axis that separates the five values is whether the element still occupies space in normal flow.

static, relative, and sticky stay in flow. Their siblings lay out around them as if nothing special happened, and the space they reserve is based on their original position.

absolute and fixed are out of flow. They are removed from the normal layout pass entirely, so siblings collapse into the space they used to occupy. This is why an absolutely positioned element cannot push anything and cannot be pushed, and why a parent whose only children are absolutely positioned collapses to zero height.

What Each One Is Offset From

The second axis is the containing block: the rectangle that top, right, bottom, and left are measured against.

static is the default. Offsets and z-index are ignored entirely; the element sits where the flow puts it.

relative keeps the element in flow and offsets it visually from its own original position. Critically, the space it originally occupied stays reserved, so shifting an element down by 20px leaves a 20px gap where it used to be and may overlap whatever is below. Nothing else in the layout moves.

absolute removes the element from flow and offsets it from the padding box of its nearest positioned ancestor, meaning the nearest ancestor whose position is anything other than static. If no such ancestor exists, the containing block is the initial containing block, which is viewport-sized. That fallback is the source of the classic bug where a close button intended for a modal corner lands in the page corner instead.

fixed removes the element from flow and offsets it from the viewport, so it does not move when the page scrolls. The important caveat is that this is only true while no ancestor establishes a containing block for fixed descendants. An ancestor with transform, filter, backdrop-filter, perspective, contain: paint, or will-change on those properties captures fixed children, which makes them scroll with that ancestor instead of staying pinned to the viewport.

sticky is a hybrid. The element stays in flow and behaves exactly like relative until its offset threshold is crossed during scroll, at which point it stays pinned like fixed, but only within the bounds of its parent. When the parent scrolls past, the sticky element leaves with it. Because it stays in flow, no gap appears and no layout shift occurs when it sticks, which is its main advantage over the older "swap to fixed on scroll" pattern.

What relative Does Even With No Offsets

position: relative with no top, right, bottom, or left looks like a no-op but is not. It does two things.

First, it makes the element a positioned ancestor, so absolutely positioned descendants anchor to it. This is the standard container-plus-badge pattern and by far the most common reason to write it.

Second, combined with a z-index value other than auto, it creates a stacking context, which changes how descendants paint relative to the rest of the page.

When Not to Use Positioning at All

A large share of positioning in older codebases exists only to centre or align things, work that flexbox and grid now do more robustly. position: absolute with top: 50% and a translate(-50%, -50%) still has its place for overlays that must not affect layout, but for centring a child inside a container, display: grid; place-items: center is shorter, does not remove the child from flow, and survives content changes without recalculation.


Key Code Explained

/* static: the default. Offsets and z-index are IGNORED. */
.default {
  position: static;
  top: 50px; /* no effect whatsoever */
}

/* relative: stays in flow, offset from its OWN original position.
   The original space stays reserved -> a 20px gap is left behind. */
.nudged {
  position: relative;
  top: 20px;
  left: 10px;
}

/* The container/badge pattern: relative with NO offsets,
   purely to become the containing block for the absolute child. */
.avatar {
  position: relative;
}
.avatar .badge {
  position: absolute;
  top: -4px;
  right: -4px;
  /* offset from .avatar's padding box.
     Without .avatar being positioned, this would anchor to the viewport. */
}

/* absolute: out of flow. Stretch to fill by setting all four offsets. */
.overlay {
  position: absolute;
  inset: 0; /* shorthand for top/right/bottom/left: 0 */
  background: rgb(0 0 0 / 0.5);
}

/* fixed: offset from the viewport, immune to scrolling */
.fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
}

/* ...unless an ancestor creates a containing block for fixed descendants */
.broken-ancestor {
  transform: translateZ(0); /* now .fab scrolls with THIS element */
}

/* sticky: relative until the threshold, then pinned within the parent.
   The threshold offset is REQUIRED — sticky with no offset never sticks. */
.section-header {
  position: sticky;
  top: 0;
}

/* Modern alternative to absolute centring */
.centered-modern {
  display: grid;
  place-items: center; /* child stays in flow, no translate needed */
}

The .avatar and .avatar .badge pair is the most important idiom in this list. It shows both halves of the relationship: the child needs absolute to escape flow and sit in the corner, and the parent needs relative purely to be the thing that corner is measured from. Remove the parent's relative and the badge jumps to the page corner, which is the single most common absolute-positioning bug.

The .broken-ancestor rule is the detail that separates a working mental model from a memorised one. fixed is described everywhere as "relative to the viewport," but a transform anywhere up the ancestor chain quietly changes that, and the resulting behaviour, a fixed element that scrolls, looks impossible until you know the rule.


Tradeoffs

ValueIn flowOffset fromScrolls with pageHonours z-index
staticYesNothing; offsets ignoredYesNo
relativeYes, original space reservedIts own original positionYesYes
absoluteNoNearest positioned ancestor's padding boxYes, with that ancestorYes
fixedNoViewport, unless captured by a transformed ancestorNoYes
stickyYesNearest scrolling ancestor, bounded by its parentUntil threshold, then pinnedYes

What Interviewers Actually Check

  • Whether you can say which values remove the element from flow and what that does to its siblings
  • Whether you can name the containing block for absolute, fixed, and relative precisely
  • Whether you know relative offsets leave the original space reserved
  • Whether you can explain what position: relative accomplishes with no offsets at all
  • Whether you know a transformed ancestor breaks fixed, rather than describing fixed as unconditionally viewport-relative

Follow-Up Questions

  1. What does inset do, and how does it relate to the four individual offset properties and their logical equivalents?
  2. If an absolutely positioned element sets both left: 0 and right: 0 but no width, what width does it take?
  3. How does position: absolute interact with display: flex on the parent? Is the element still a flex item?
  4. Why does a parent containing only absolutely positioned children collapse to zero height, and what are two ways to fix it?
  5. What problem do CSS anchor positioning and position-area solve that absolute positioning alone could not?

Common Candidate Mistakes

  • Forgetting that position: relative reserves the element's original space, then being surprised by a gap above and an overlap below after nudging it
  • Writing position: absolute without checking that an ancestor is positioned, so the element silently anchors to the initial containing block and lands in a page corner
  • Describing fixed as always viewport-relative, missing that a transform, filter, or will-change on any ancestor captures it and makes it scroll
  • Treating sticky as a flavour of fixed, which fails to explain why it stops at its parent's boundary and why it causes no layout shift
  • Reaching for absolute positioning to centre or align content when place-items: center or flexbox alignment keeps the element in flow and survives content changes

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you say which of the five values remove the element from normal flow?
  • Can you name the containing block for absolute, fixed, and relative?
  • Can you explain why relative offsets leave a gap behind?
  • Can you explain what position: relative enables when no offsets are set?
  • Can you pick the right value for a badge, a full-screen modal, a pinned header, and a sidebar, and justify each?

Summary

The five position values divide first by flow participation. static, relative, and sticky remain in normal flow and keep reserving space; absolute and fixed are removed from flow, so siblings close up behind them and they can neither push nor be pushed.

They divide second by containing block. static ignores offsets completely. relative offsets from the element's own original position while leaving that space reserved, which is why nudging it creates a gap and an overlap rather than moving neighbours. absolute offsets from the padding box of the nearest non-static ancestor, falling back to a viewport-sized initial containing block when none exists. fixed offsets from the viewport, but only while no ancestor has a transform, filter, or similar property that captures fixed descendants. sticky behaves as relative until its threshold is crossed, then pins like fixed within its parent's bounds, staying in flow the whole time so nothing shifts.

Two practical points matter beyond the definitions. position: relative with no offsets is a real and deliberate declaration: it makes an element the anchor for absolute descendants and, with a z-index, creates a stacking context. And a great deal of legacy absolute positioning exists only to align things, work that place-items: center or flexbox now does without removing anything from flow.

Frequently Asked Questions

What is a containing block?

The rectangle an offset element is positioned against. For absolute it is the padding box of the nearest positioned ancestor; for fixed it is normally the viewport; for relative it is the element own original position.

Why does position: relative with no offsets still change things?

Because it makes the element a positioned ancestor for absolute descendants, and it creates a stacking context once z-index is set. Both effects apply even with no top, right, bottom, or left.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement