concurrency
errno
thread-safety
programming
C language

Is errno thread-safe?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

On modern POSIX systems and mainstream C runtimes, errno is thread-safe in the sense that each thread gets its own logical errno value. That does not mean errno is magical or immune to misuse, though. You still need to read it immediately after the call that failed and avoid assuming it keeps its old value forever.

What errno Really Is

errno is not usually a single global integer shared by every thread. In modern implementations it is typically a macro that resolves to thread-local storage, so one thread's error state does not overwrite another's.

This matters because system calls and library functions often report failure like this:

c
1#include <errno.h>
2#include <fcntl.h>
3#include <stdio.h>
4#include <string.h>
5#include <unistd.h>
6
7int main(void) {
8    int fd = open("/path/that/does/not/exist", O_RDONLY);
9    if (fd == -1) {
10        printf("errno=%d, message=%s\n", errno, strerror(errno));
11    }
12    return 0;
13}

If two threads call failing functions at the same time, each thread still sees its own errno.

Why It Is Considered Thread-Safe

Thread safety here means thread isolation, not that every possible use is automatically safe. The runtime ensures that when thread A sets errno, thread B does not see or overwrite that same storage location.

A small pthread example demonstrates the idea:

c
1#include <errno.h>
2#include <fcntl.h>
3#include <pthread.h>
4#include <stdio.h>
5#include <string.h>
6#include <unistd.h>
7
8void* worker(void* arg) {
9    const char* path = (const char*)arg;
10    int fd = open(path, O_RDONLY);
11
12    if (fd == -1) {
13        printf("thread for %s -> errno=%d (%s)\n", path, errno, strerror(errno));
14    }
15
16    return NULL;
17}
18
19int main(void) {
20    pthread_t t1, t2;
21
22    pthread_create(&t1, NULL, worker, "/missing/file/a");
23    pthread_create(&t2, NULL, worker, "/missing/file/b");
24
25    pthread_join(t1, NULL);
26    pthread_join(t2, NULL);
27    return 0;
28}

Both threads may hit an error, but each reads its own thread-local errno.

The Important Practical Rule

Even though errno is thread-local, you still have to capture it immediately after the failure you care about. Many other functions can modify errno, including helper functions you call while logging or formatting output.

This is the safe pattern:

c
1int fd = open("missing.txt", O_RDONLY);
2if (fd == -1) {
3    int saved_errno = errno;
4    fprintf(stderr, "open failed: %s\n", strerror(saved_errno));
5}

Saving the value avoids surprises if later calls change it before you finish reporting the failure.

What Thread-Safe Does Not Mean

It does not mean:

  • 'errno is reset to zero before every function call'
  • every failed function must set it in a way you expect
  • it is safe to inspect errno if the function did not indicate failure

Many functions only promise meaningful errno values when they explicitly report failure. If a function succeeds, errno may still hold some older unrelated value.

Common Pitfalls

The biggest mistake is checking errno without checking the function's documented return value first. If the call succeeded, errno may contain stale data from a previous unrelated failure.

Another issue is calling other library functions before preserving the error code. Even though another thread will not overwrite your thread's errno, your own thread can overwrite it by making additional calls.

Developers also sometimes assume thread-safe means portable to every historical system ever built. In practice, modern POSIX environments behave correctly, but very old or unusual environments deserve verification.

Finally, note that strerror() itself has thread-safety caveats on some platforms. If you need stricter control, use the thread-safe variant available on your platform, such as strerror_r.

Summary

  • 'errno is thread-safe on modern systems because it is effectively thread-local.'
  • One thread's error code does not overwrite another thread's errno.
  • Read or save errno immediately after the failing call you care about.
  • Never inspect errno unless the function's return value indicates failure.
  • Thread-safe errno still requires careful usage patterns in your own code.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.