skip to content

How do os.CreateTemp and os.MkdirTemp name what they create, and who is responsible for deleting it?

level: middleimportance: should knowfreq 44%

answer

  1. the pattern is not a glob
  2. where does the random part go?
  3. one gives you a handle, one gives you a name
  4. why not just use a timestamp and PID?
  5. nothing sweeps up after your program

basics

~20 s

Both take a directory and a pattern; a random string replaces the last asterisk in the pattern, or is appended when there is none. An empty directory argument means os.TempDir. Neither cleans up: the caller must remove the file or directory itself.

solid answer

~40 s

`os.CreateTemp(dir, pattern string) (*os.File, error)` and `os.MkdirTemp(dir, pattern string) (string, error)` both build a unique name from `pattern`: if it contains a `*`, the random string replaces the **last** one — so `"gen-*.go"` keeps the extension — otherwise the random string is appended. Passing `""` for `dir` means `os.TempDir()`, which is `$TMPDIR` or `/tmp` on Unix. Uniqueness is real, not probabilistic hand-waving: the file is created with `O_CREATE|O_EXCL` and the call retries on collision, so two concurrent callers cannot get the same name. `os.CreateTemp` returns an **already open** `*os.File` with mode `0600`; ask it for its path with `f.Name()`. `os.MkdirTemp` returns just the path, with the directory created `0700`. Neither registers any cleanup — the caller removes them, typically with `os.Remove` and `os.RemoveAll`.

code

go · 13 lines
go
dir, err := os.MkdirTemp("", "gen-*")
if err != nil {
	return err
}
// Nothing else will delete it.
defer os.RemoveAll(dir)

// Returns an already-open file, mode 0600.
f, err := os.CreateTemp(dir, "model-*.go")
if err != nil {
	return err
}
fmt.Println(f.Name()) // .../gen-.../model-3049182736.go

go deeper

for a junior

Know that both calls exist and what each returns: an open file from one, a directory path from the other. Remember that you have to delete what they create.

for a middle

Explain the pattern rule precisely — the random string replaces the last asterisk, or is appended when there is none — plus the empty-dir default and the owner-only modes.

for a senior

Argue why these calls exist rather than a hand-rolled name: the create-exclusive retry closes a race on a shared temp directory, and the 0600 and 0700 modes close a disclosure window that a later chmod would not.

for a principal

Set the policy: where temporary output for a build or a service is staged, whether the default temp directory is even the right filesystem for it, and how the team guarantees cleanup on the paths where a process can die mid-run.

## Two calls, one naming rule ```go func os.CreateTemp(dir, pattern string) (*os.File, error) func os.MkdirTemp(dir, pattern string) (string, error) ``` They are siblings: one makes a temporary file, the other a temporary directory, and they share their argument conventions exactly. ## The pattern and the asterisk The `pattern` is not a glob. It is a template with one optional placeholder: - If it contains a `*`, the generated random string replaces the **last** `*`. `"gen-*.go"` becomes something like `gen-1837462910.go`, keeping the suffix — which matters when the file has to be recognisable by extension, for instance a `.go` file a later step will parse. - If it contains no `*`, the random string is appended to the end. `"gen-"` becomes `gen-1837462910`. That single rule is worth memorising, because the naive assumption — that the random part always lands at the end — silently produces `model.go1837462910` when what you wanted was `model-1837462910.go`. The pattern must not contain a path separator; the call returns an error if it does, so you cannot smuggle a subdirectory into it. ## The dir argument An empty `dir` means "use the default temporary directory", which is `os.TempDir()`. On Unix that is `$TMPDIR` if set, otherwise `/tmp`; on Windows it comes from the platform's temp-path lookup. Passing a real directory instead puts the temporary object there — useful when you want everything a run produces to sit under one root, or when the default temp directory is on a different filesystem from where the work has to end up. ## Uniqueness is enforced, not hoped for `os.CreateTemp` does not generate a name and trust it. It opens with `O_CREATE|O_EXCL`, which fails if the name already exists, and retries with a fresh random string on collision. `os.MkdirTemp` does the same with `Mkdir`, which fails on an existing directory. So the guarantee is genuine even with many processes racing in the same temp directory: whoever gets the name created it, and everyone else moves on. Constructing a temp name yourself with a timestamp or a PID and then opening it is exactly the pattern these calls exist to replace. ## What each returns `os.CreateTemp` returns an **open** `*os.File`, opened read-write with mode `0600`, meaning owner-only. That is deliberate: on a shared temp directory a world-readable file is a leak, so the file is private from the instant it exists rather than being chmodded afterwards. You get the path from `f.Name()`. Because it is already open, you write to it directly — there is no second open, and no window in which someone else could substitute the path. `os.MkdirTemp` returns the **path string** of the new directory, created with mode `0700` — again owner-only. There is nothing to close. Note the asymmetry: one hands you a handle, the other a name. Forgetting that `os.CreateTemp` returns a file and trying to open the result again is a common slip. ## Cleanup is entirely yours Neither call arranges for anything to be deleted. There is no finaliser, no runtime hook, no exit handler. If your program creates temporary files and never removes them, they accumulate until whatever cleans `/tmp` on that machine gets round to it — which on many systems is a boot, or never. The conventional shapes are `defer os.Remove(f.Name())` for a file and `defer os.RemoveAll(dir)` for a directory, placed immediately after the error check so the cleanup is registered before anything can return early. In tests you can skip the bookkeeping entirely: `t.TempDir()` from the `testing` package creates a directory for the test and removes it when the test and its subtests finish. ## Where a code generator uses them A generator that emits a package's worth of files often stages the whole output in one temporary directory: `os.MkdirTemp("", "gen-*")` gives it a private root, `os.CreateTemp(dir, "model-*.go")` gives each emitted file a unique, extension-preserving name inside it, and a single `os.RemoveAll(dir)` disposes of everything if the run fails. The private `0700` mode means intermediate output is not readable by other users on a shared build machine while it is being produced. ## History These were `ioutil.TempFile` and `ioutil.TempDir` before Go 1.16, with the same arguments. The 1.16 move to `os` renamed them to `CreateTemp` and `MkdirTemp`, matching `os.Create` and `os.Mkdir`. ## What an interviewer is checking That you know the `*` rule rather than guessing where the random part lands; that you can say why the calls exist at all (the `O_EXCL` retry, not the randomness); that you know one returns a handle and the other a name, both owner-only; and that you never assume something else will clean up after you.

  • Why is os.CreateTemp safer than building a name yourself and calling os.Create on it?
    Because it creates with `O_CREATE|O_EXCL` and retries on collision, so the name is guaranteed unclaimed at the moment you get it. A hand-built name has a gap between choosing it and opening it, during which another process — or an attacker on a shared temp directory — can take it or plant a symlink there.
  • What mode do the file and directory these calls create have, and why does it matter?
    The file is `0600` and the directory `0700` — owner-only in both cases. Temporary objects usually live in a directory every user on the machine can write to, so anything world-readable leaks the contents to other users for as long as it exists. Being private from creation leaves no window at all.
  • os.CreateTemp("", "model.go") — what filename do you actually get?
    Something like `model.go2841937465`. With no `*` in the pattern the random string is appended, so the `.go` extension ends up in the middle of the name and any later step matching on the extension will skip the file. You want `"model-*.go"` instead.
  • In a test, is there a way to avoid the cleanup bookkeeping?
    Yes — `t.TempDir()` on `testing.T` creates a directory scoped to that test and removes it, and everything under it, once the test and its subtests finish. It is the right default inside tests; `os.MkdirTemp` is for production code, where you own the removal.

saying these in an interview costs you the question

  • Thinks the random string always goes at the end of the pattern
  • Treats the pattern as a shell glob
  • Calls os.Open on the path os.CreateTemp already opened
  • Assumes something deletes temporary files automatically
  • Builds temp names from a PID and a timestamp instead
  • Expects os.MkdirTemp to return an open handle