Path Resolution with Symbolic Links
AnthropicSoftware Engineer · OnsiteLast updated
Implement a filesystem path resolution function that normalizes paths containing '.', '..', and symbolic links while detecting cycles.
Problem Statement
Implement a function resolve_path(symlinks: dict[str, str], path: str) -> str | None that simulates filesystem path canonicalization (similar to POSIX realpath).
The filesystem model has the following rules:
- All paths are resolved starting from the root directory
/. - Path components are separated by
/. Multiple consecutive slashes (e.g.,//) should be treated as a single/. - A component
.refers to the current directory and can be ignored. - A component
..refers to the parent directory. Going above/remains at/. symlinksis a dictionary where each key is a fully resolved, canonical absolute path pointing to a target path string. The target path may be absolute (e.g.,/var/data) or relative (e.g.,../shared).- When navigating path components from left to right, if the path resolved so far matches a key in
symlinks, that prefix must be replaced by the symlink's target before processing continues:- If the target is an absolute path, resolution restarts from
/with the new target. - If the target is a relative path, it resolves relative to the parent directory of the symlink.
- If the target is an absolute path, resolution restarts from
- If a symbolic link loop (cycle) or excessive indirection (more than 40 symlink expansions) is encountered, return
None.
The final returned path must be canonical: starting with /, containing no . or .., no trailing slashes (except for the root / itself), and having all symbolic links fully resolved.
Interface
def resolve_path(symlinks: dict[str, str], path: str) -> str | None:
pass
Constraints
1 <= len(path) <= 10000 <= len(symlinks) <= 200- Keys and values in
symlinkshave lengths between1and1000. - Path components consist of alphanumeric characters, underscores, hyphens, and dots.
- The maximum number of symlink dereferences allowed during resolution is
40.
Examples
Example 1
symlinks = {
"/usr/local/bin": "/opt/bin"
}
path = "/usr/local/bin/python"
# Output: "/opt/bin/python"
# Explanation:
# Resolving "/usr/local/bin" matches a symlink pointing to "/opt/bin".
# The remainder "python" is appended, yielding "/opt/bin/python".
Example 2
symlinks = {
"/a/b": "../c",
"/c": "/d"
}
path = "/a/b/test"
# Output: "/d/test"
# Explanation:
# 1. Navigating to "/a/b" triggers the symlink "../c".
# 2. "../c" relative to "/a/b"'s parent ("/a") resolves to "/c".
# 3. "/c" is also a symlink pointing to "/d".
# 4. Appending the remaining component "test" yields "/d/test".
Example 3
symlinks = {
"/loop": "/loop/nested"
}
path = "/loop"
# Output: None
# Explanation: The symlink forms an infinite expansion loop.
Edge Cases
- Root directory edge cases:
resolve_path({}, "/../../..")should return"/". - Symlink targets that themselves contain
.or..or point to other symlinks. - Trailing slashes in the input path or symlink targets.
Hints
Hint 1
Process path components sequentially from left to right using a stack to keep track of the resolved canonical directory chain.
Hint 2
After resolving each component, check if the current canonical path prefix exists in the symlink dictionary.
Hint 3
When a symlink is encountered, replace the current path prefix with the target and re-prepend any remaining unprocessed path components; track total dereference count to catch cycles.
Follow-up questions
- How would you optimize path lookups if symlinks formed a large, static tree queried millions of times?
- How does POSIX path resolution handle dangling symlinks or permissions at intermediate path components?
- How would you adapt your algorithm to run concurrently in a multi-threaded filesystem where symlinks can be modified while being resolved?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.