os.path.join

join puts exactly one separator between components ('/' with posixpath, '\' with ntpath) and never touches the disk. The rule that surprises people: a component that is absolute — '/etc' on POSIX, 'C:\' or '\\server\share' on Windows — throws away everything before it.

os.path functionPython 3.0+Live demo
Common call
os.path.join(base, 'data', name)
Returns
'base/data/name' (with '\' on Windows)
Replaces
base + '/' + name
Watch out
An absolute later component (or a user-supplied "/etc/passwd") replaces the start
os.path.join(pathpath — The first component.type: str | bytes | PathLike · required, *paths)
→ str | bytes

Demo

Live evaluation
Type the components; posixpath shows the Linux/macOS result on any machine.
Try:
Inputs
partslist[str]components, comma-separated
Code
import posixpath
posixpath.join(*['/home/ada', 'projects', 'site', 'index.html'])
Result
'/home/ada/projects/site/index.html'

posixpath only adds '/' where the previous component does not already end with one, so 'logs/' + '2026/' + 'app.log' gives no doubled slashes. '/etc/passwd' is absolute, so '/srv/app/static' is discarded. In the second tab ntpath uses '\' (shown doubled in the repr) and treats 'C:' as a drive: ntpath.join('C:', 'Users') is 'C:Users' — relative to the current folder on drive C, not 'C:\Users'. Both modules treat '/tmp/x' as rooted and drop 'project'.

Parameters

NameTypeRequiredDescription
pathstr | bytes | PathLikeyesThe first component.
*pathsstr | bytes | PathLikenoMore components. All str or all bytes — mixing raises TypeError. An empty last component adds a trailing separator.

Return value

str | bytes — The joined path. Nothing is checked on disk.

Common patterns

Path next to the current script
__file__ is relative to where the script lives, not to the cwd.
import os
config = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.toml')
Join a list of components
Unpack the list with *.
import os
parts = ['data', '2026', 'report.csv']
path = os.path.join(*parts)
Keep user-supplied names inside a folder
Normalize, then check the result is still under the base.
import os
def safe_join(base, name):
    p = os.path.abspath(os.path.join(base, name))
    if os.path.commonpath([os.path.abspath(base), p]) != os.path.abspath(base):
        raise ValueError('outside the base folder')
    return p
Always forward slashes (URLs, archives, config keys)
posixpath behaves the same on every OS.
import posixpath
key = posixpath.join('assets', 'img', 'logo.png')

Examples

1. Three components
import posixpath posixpath.join('/home/ada', 'docs', 'a.txt')
Returns
'/home/ada/docs/a.txt'
2. An absolute part wins
import posixpath posixpath.join('/srv/app', '/etc/passwd')
Returns
'/etc/passwd'
3. Empty last part: trailing separator
import posixpath posixpath.join('build', '')
Returns
'build/'
4. Windows separators
import ntpath ntpath.join(r'C:\Users', 'ada', 'notes.txt')
Returns
'C:\\Users\\ada\\notes.txt'
5. Windows: another drive restarts
import ntpath ntpath.join(r'C:\data', 'D:report.txt')
Returns
'D:report.txt'
6. Path objects are accepted
import posixpath from pathlib import PurePosixPath posixpath.join(PurePosixPath('src'), 'app.py')
Returns
'src/app.py'
7. str and bytes do not mix
import posixpath posixpath.join('data', b'raw')
Returns
TypeError: Can't mix strings and bytes in path components

Pitfalls

1. Passing a list
join takes components as separate arguments.
join(list)
import posixpath
posixpath.join(['data', 'raw'])
TypeError: expected str, bytes or os.PathLike object, not list
join(*list)
import posixpath
posixpath.join(*['data', 'raw'])
'data/raw'
2. A leading slash on a later component
'/img/logo.png' is absolute, so the folder before it is dropped. Strip the slash if the part is meant to be relative.
'/img/logo.png'
import posixpath
posixpath.join('static', '/img/logo.png')
'/img/logo.png'
lstrip('/')
import posixpath
posixpath.join('static', '/img/logo.png'.lstrip('/'))
'static/img/logo.png'
3. Building URLs with os.path.join
On Windows os.path is ntpath and inserts a backslash. URLs always use /.
what Windows does
import ntpath
ntpath.join('https://example.com/api', 'v1')
'https://example.com/api\\v1'
posixpath (or urljoin)
import posixpath
posixpath.join('https://example.com/api', 'v1')
'https://example.com/api/v1'

When to use

Use it
  • Combining a folder and a file name you got as strings
  • Code that must produce native paths on each OS
Reach for something else
  • Object paths → Path('a') / 'b' (pathlib)
  • URLs → urllib.parse.urljoin (or posixpath for the path part)
  • Untrusted names → validate the result (commonpath) — join will happily escape the base

Notes

CPython impl
Lib/posixpath.py join: start from the first part; for each next part, restart if it starts with "/", otherwise add "/" unless the path is empty or already ends with one. Lib/ntpath.py join works on (drive, root, tail) triples from splitroot
Windows
ntpath.join('C:', 'x') is 'C:x' (drive-relative); a component with a different drive restarts the path; a rooted component like '\x' keeps the current drive
No normalization
join never removes '..' or '.' — combine with normpath or abspath when you need that

FAQ

Because a later argument is absolute — it starts with '/' (or with a drive or '\' on Windows). join then starts over from that component. Strip the leading separator if it was meant to be relative.