Skip to content
Codeloom
DSA

Simplify Unix Path Using a Stack — LeetCode 71 Solution

Solve Simplify Path LeetCode 71 with a stack. Handle ., .., multiple slashes, and edge cases. Python solution with step-by-step trace.

·4 min read · By Codeloom
Intermediate 14 min read

What you'll learn

  • How Unix path resolution works with . and ..
  • Stack-based approach for directory navigation
  • Handling edge cases: trailing slashes, root directory, multiple slashes
  • Why this is a natural stack problem

Prerequisites

Stack-based Unix path simplification showing directory navigation

Given an absolute Unix-style file path, simplify it to its canonical form. This means:

  • No trailing slashes (except root /)
  • No double slashes //
  • No . (current directory)
  • No .. beyond root
  • Resolve .. to go up one directory

This is LeetCode 71.

Unix Path Rules

ComponentMeaningAction
.Current directoryIgnore
..Parent directoryPop from stack
nameDirectory namePush to stack
/SeparatorSplit on it
//Same as /Handled by split

Solution

def simplify_path(path: str) -> str:
    """
    Simplify Unix path to canonical form.
    LeetCode 71.
    Time: O(n), Space: O(n)
    """
    stack = []

    # Split by '/' — handles multiple slashes automatically
    for component in path.split('/'):
        if component == '..':
            if stack:
                stack.pop()  # go up one directory
        elif component and component != '.':
            stack.append(component)  # valid directory name

    return '/' + '/'.join(stack)

That is the entire solution. The split('/') call handles multiple consecutive slashes by producing empty strings, which we skip with the component truthiness check.

Step-by-Step Trace

Example 1: "/home//foo/../bar"

Split by '/': ['', 'home', '', 'foo', '..', 'bar']

Component    Action           Stack
─────────    ──────           ─────
''           skip (empty)     []
'home'       push             ['home']
''           skip (empty)     ['home']
'foo'        push             ['home', 'foo']
'..'         pop              ['home']
'bar'        push             ['home', 'bar']

Result: '/home/bar' ✓

Example 2: "/../a/b/../../c/"

Split by '/': ['', '..', 'a', 'b', '..', '..', 'c', '']

Component    Action           Stack
─────────    ──────           ─────
''           skip             []
'..'         pop? empty!      []          ← can't go above root
'a'          push             ['a']
'b'          push             ['a', 'b']
'..'         pop              ['a']
'..'         pop              []
'c'          push             ['c']
''           skip             ['c']

Result: '/c' ✓

Example 3: "/a/./b/../../c/"

Split by '/': ['', 'a', '.', 'b', '..', '..', 'c', '']

Component    Action           Stack
─────────    ──────           ─────
''           skip             []
'a'          push             ['a']
'.'          skip (current)   ['a']
'b'          push             ['a', 'b']
'..'         pop              ['a']
'..'         pop              []
'c'          push             ['c']
''           skip             ['c']

Result: '/c' ✓

Why a Stack?

Directory navigation is inherently stack-like:

  • Entering a directory = push onto stack
  • Going back (..) = pop from stack
  • Current directory (.) = no operation

The stack maintains the path from root to current position. At the end, joining the stack gives the canonical path.

Edge Cases

# Root directory
assert simplify_path("/") == "/"

# Multiple dots (valid directory name!)
assert simplify_path("/...") == "/..."

# Just go up
assert simplify_path("/../../../") == "/"

# Current directory
assert simplify_path("/./././.") == "/"

# Trailing slash
assert simplify_path("/home/user/") == "/home/user"

# Multiple slashes
assert simplify_path("///a///b///") == "/a/b"

# Hidden files (start with .)
assert simplify_path("/.hidden") == "/.hidden"

# Complex mix
assert simplify_path("/a/b/c/../d/./e/../..") == "/a/b"

Common Gotcha: ... Is a Valid Name

Three or more dots is a valid directory name, not a special symbol:

simplify_path("/a/.../b")  # → "/a/.../b"  (... is a real name)
simplify_path("/a/..b/c")  # → "/a/..b/c"  (..b is a real name)

Only exactly . and exactly .. have special meaning.

Alternative: Without Split

If the interviewer asks you to avoid split():

def simplify_path_manual(path: str) -> str:
    """
    Parse path character by character.
    Time: O(n), Space: O(n)
    """
    stack = []
    i = 0
    n = len(path)

    while i < n:
        # Skip slashes
        while i < n and path[i] == '/':
            i += 1

        # Build component
        start = i
        while i < n and path[i] != '/':
            i += 1
        component = path[start:i]

        if component == '..':
            if stack:
                stack.pop()
        elif component and component != '.':
            stack.append(component)

    return '/' + '/'.join(stack)

Complexity

MetricValueWhy
TimeO(n)Single pass through the string
SpaceO(n)Stack stores directory names

When to Use This Pattern

  • File system operations: Path normalization in real file systems
  • URL normalization: Canonicalizing URLs follows similar rules
  • Breadcrumb navigation: Building navigation trails in UIs
  • Undo mechanisms: Stack-based history with “go back” support