Skip to content
intermediate

Simplify Path

Treating this as string cleanup is how you lose the problem. Removing every dot breaks valid names such as ..., while careless parent handling can let /../…

Published 2026-09-07Updated 2026-09-129 min read
Group of teenagers using a vending machine in a school corridor, captured in ambient lighting.
Group of teenagers using a vending machine in a school corridor, captured in ambient lighting. Photo by Berk Aktas on Pexels.
Problem

Simplify Path

Difficulty: MediumAcceptance rate: 51.3%

Given an absolute Unix-style path, return its simplified canonical path by resolving current-directory markers '.', parent-directory markers '..', and redundant slashes while preserving valid names such as '...' or '....'.

StringStack

Constraints

  • 1 <= path.length <= 3000
  • path consists of English letters, digits, '.', '/', or '_'
  • path is a valid absolute Unix path

Important details

  • The result starts with exactly one slash and uses exactly one slash between directory names.
  • The result has no trailing slash unless it is the root path.
  • A single period denotes the current directory and a double period denotes the parent directory; moving above the root leaves the path at the root.
  • Other sequences of periods are ordinary directory or file names.
  • Return the canonical path.

Treating this as string cleanup is how you lose the problem. Removing every dot breaks valid names such as ..., while careless parent handling can let /../ escape the root.

The reliable Simplify Path solution is a left-to-right simulation:

  1. Split the path into slash-separated components.
  2. Ignore empty components and the exact token ".".
  3. Pop the most recent component for ".." when possible.
  4. Push every other component.
  5. Reconstruct the answer from the stack.

The stack represents the current canonical path. Once that meaning is correct, the code becomes small.

Read the Path Contract First

The input is an absolute Unix-style path. It begins with /, has length at most 3000, and uses letters, digits, periods, slashes, and underscores.

The output must:

  • Begin with exactly one /.
  • Use exactly one / between components.
  • Have no trailing slash unless the result is the root path /.
  • Remove the control tokens "." and ".." from the final representation.
  • Treat repeated slashes as a single separator.
  • Never move above the root.

The crucial detail is exact token matching:

ComponentMeaning
""Empty component caused by repeated, leading, or trailing slashes
"."Current directory; no state change
".."Parent directory; remove the latest component if one exists
Anything elseAn ordinary directory or file-name component

So "..." and "...." are ordinary names. They are not variations of "." or "..".

This is a canonical path parser under the rules supplied by the problem. It is lexical processing: we are resolving the characters and components in the input, not asking the operating system to inspect a real filesystem or resolve symbolic links.

Make the Stack the Current Path

The recognition clue is simple:

Components are processed in order, and ".." cancels the most recently entered component.

That is last-added-first-removed behavior. A stack fits the state transition exactly.

Define the stack precisely:

Each stack entry is an ordinary component currently present in the canonical path, stored from root toward the current location.

For example, after processing /home/user, the stack is:

["home", "user"]

The stack does not store slashes, ".", or "..". It stores only the names that survive canonicalization.

Each component produces one of four transitions:

  • Empty component: ignore it.
  • ".": ignore it because the current location does not change.
  • "..": pop the stack if it is nonempty.
  • Ordinary name: append it to the stack.

An empty stack represents the root /. That gives root clamping for free: if ".." appears at the root, there is nothing to pop, so the path stays at /.

This is related to the broader stack pattern used for nested structures, but the meaning is different. Here, the stack is not matching opening and closing symbols. It is modeling reversible navigation state.

Derive the One-Pass Algorithm

Flowchart showing path components entering a parser: empty components and "." are ignored, ".." pops the stack when possible, and all other names are pushed; the final stack is joined with slashes.
Classify each component once; the stack remains the current canonical path throughout the scan.

A tempting baseline is repeated string replacement:

  • Remove "/./".
  • Collapse "//".
  • Remove the component before "/..".
  • Repeat until nothing changes.

The problem is that string rewriting loses the clean boundary between components. A replacement can create a new pattern that needs another scan, and the interactions between slashes, dots, and parent moves become difficult to reason about.

The stack preserves those boundaries. We classify each component once, update the current state once, and reconstruct only after the scan.

The pseudocode is:

stack = []

for component in path split by "/":
    if component is empty or component == ".":
        continue

    if component == "..":
        if stack is not empty:
            pop stack
    else:
        push component

return "/" + join(stack, "/")

The exact equality checks matter. Do not use a test such as “the component contains only periods” or “the component starts with dots.” The contract gives special meaning only to "." and "..".

For example:

"...": ordinary name
"....": ordinary name
".hidden": ordinary name
"a..b": ordinary name

Only the two exact control tokens change the stack structure.

Prove the Stack Invariant

The implementation is short enough to memorize, but memorization is not the useful skill. The useful skill is knowing why every branch is safe.

Use this invariant:

After processing any prefix of the components, the stack contains exactly the ordinary components remaining in that prefix’s canonical path, in root-to-current order.

Now check each case.

Empty components and "."

An empty component comes from repeated or boundary slashes. Under the problem rules, those slashes do not add a directory.

The component "." means the current directory. Staying where you are also adds nothing.

In both cases, leaving the stack unchanged preserves the canonical path.

An ordinary component

An ordinary component names a new location below the current one. It must appear at the end of the canonical path, so pushing it extends the stack correctly.

For example:

["home", "user"] + "docs"
→ ["home", "user", "docs"]

The ".." component

".." moves to the parent directory. The current component is therefore removed, which is exactly pop().

If the stack is empty, the current location is already root. There is no parent above root, so doing nothing is correct.

This handles both:

["home", "user"] + ".."
→ ["home"]

and:

[] + ".."
→ []

Because every branch preserves the invariant, it holds after the entire path has been processed. At that point, the stack contains precisely the components of the canonical result.

Joining the stack with / removes redundant separators. Adding one leading slash satisfies the output format:

  • [] becomes "/".
  • ["home"] becomes "/home".
  • ["home", "user"] becomes "/home/user".

No separate trailing-slash cleanup is necessary.

Trace the Boundaries Before Coding

A dry run is most useful when it attacks the assumptions likely to be wrong.

Ordinary navigation and backtracking

For:

/home/user/Documents/../Pictures
ComponentActionStack after actionReason
""Ignore[]Leading slash
homePush["home"]Ordinary name
userPush["home", "user"]Ordinary name
DocumentsPush["home", "user", "Documents"]Ordinary name
..Pop["home", "user"]Return to parent
PicturesPush["home", "user", "Pictures"]Ordinary name

The result is:

/home/user/Pictures

Ellipses are names

Now consider:

/.../a/../b/c/../d/./

The component "..." must be pushed. It is not "." and it is not "..".

The important state changes are:

"..."   → ["..."]
"a"     → ["...", "a"]
".."    → ["..."]
"b"     → ["...", "b"]
"c"     → ["...", "b", "c"]
".."    → ["...", "b"]
"d"     → ["...", "b", "d"]
"."     → ["...", "b", "d"]

The canonical result is:

/.../b/d

A broad “all dots are special” check fails this example immediately.

Parent moves at root

For:

/../../x

the first ".." sees an empty stack and does nothing. The second does the same. Then "x" is pushed:

[] → [] → [] → ["x"]

The result is:

/x

Root is a boundary, not a component that should be pushed. Never store ".." in the stack when there is nothing to remove; that would turn an absolute path into an invalid relative-looking result.

Repeated and trailing slashes

For:

/home//foo/

splitting produces empty components around the repeated and trailing slashes. Ignoring those components leaves:

["home", "foo"]

Joining once produces:

/home/foo

The parser handles both duplicate separators and trailing separators through the same empty-component rule.

Implement the Python Solution

Here is a self-contained Python implementation:

class Solution:
    def simplifyPath(self, path: str) -> str:
        stack = []

        for component in path.split("/"):
            if component == "" or component == ".":
                continue

            if component == "..":
                if stack:
                    stack.pop()
            else:
                stack.append(component)

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

Each variable has a narrow job:

  • path.split("/") exposes the component boundaries.
  • stack stores the ordinary components that remain in the canonical path.
  • The first branch removes components that do not change location.
  • The second branch applies a parent move while clamping at root.
  • The final branch preserves every ordinary name, including "...".
  • "/" + "/".join(stack) reconstructs the required absolute format.

The final expression deserves attention. When stack is empty:

"/" + "/".join([])

becomes:

"/"

When the stack is nonempty, join inserts exactly one separator between components. This is better than building a string with repeated conditional trimming because the output format follows directly from the data structure.

A standard filesystem helper would be the wrong abstraction here. The problem gives a small, explicit lexical contract, and the direct parser makes every rule visible during an interview.

Complexity and Submission Checks

Let n be the path length.

  • Time: O(n). Splitting, scanning the components, and joining the surviving components each take time proportional to the input size.
  • Auxiliary space: O(n) in the worst case. The split result and the stack can both contain information proportional to the path length.

The stack entries are each pushed at most once and popped at most once. There is no repeated search for the previous slash or repeated rewriting of the path.

Before submitting, test the boundaries that expose incorrect assumptions:

  • Root-only input: /
  • A trailing slash: /home/
  • Repeated slashes: /home//foo/
  • Current-directory markers: /a/./b
  • Parent moves: /a/b/../c
  • Several parent moves: /a/b/../../c
  • Moving above root: /../ or /../../x
  • A valid ellipsis name: /.../a
  • Longer period names: /..../x
  • Backtracking through multiple components: /a/b/c/../../d

The reusable recognition rule is this:

When tokens arrive left to right, some tokens add nested state, and a later token cancels the most recent active state, define stack entries semantically, process each token once, and prove a prefix invariant before coding.

For this problem, the stack is the current canonical path. Classify exact tokens. Clamp parent moves at root. Reconstruct once. That is the whole mechanism—and it is much stronger than memorizing a string-cleanup trick.

References

  1. Simplify Path - LeetCodeleetcode.com
7sources checked
7source domains
5searches run

Research updated Sep 7, 2026

Related sites

Strengthen the language foundations behind the solution

Use LearnPyFast and LearnJSFast when you want to reinforce the language mechanics that support interview implementations.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast

Keep grinding

Related coding interview problems

Continue with nearby problems that reuse the same data-structure, invariant, or optimization pattern.

Detailed view of a computer screen displaying code with a menu of AI actions, illustrating modern software development.
beginner
9 min read

Valid Parentheses

Counting brackets tells you how many openings and closings exist. A stack tells you whether they close in the only order that nesting allows.

View solution