23 October 2026
When engineers discuss ethics in software, the conversation usually drifts toward policy, governance, or the intentions of the people writing the code. The programming language itself rarely gets a seat at the table. That is a mistake. A language is not a neutral vessel for logic. It shapes what developers can see, what they can ignore, what the compiler or runtime enforces, and what the organization must do by hand. Those differences have real ethical consequences, because most ethical failures in software are not the result of malice. They are the result of things that were easy to overlook, hard to express, or never checked at all.
This article examines how programming languages influence ethical outcomes in practice. It covers type systems, memory safety, error handling, concurrency models, dependency ecosystems, tooling, accessibility, and the human factors that determine whether a language's safeguards actually get used. It also addresses the limits of language-level solutions, because no syntax will save a team that has decided not to care.

Every one of those questions has a technical dimension, and the language sits directly on top of it. Consider a simple example. A financial system calculates interest using floating point numbers. The result is off by a fraction of a cent. Multiply that across millions of transactions and you have a systematic error that disadvantages some customers. A language with a strong numeric type system and a culture of using decimal types for money makes that mistake harder. A language where everything is a double makes it the path of least resistance. The ethical failure is not in the arithmetic. It is in the gap between what the developer intended and what the language made easy.
This is the core argument: languages are tools for managing attention. They determine which classes of mistakes are caught automatically and which ones depend on human vigilance. Since human vigilance is finite and unreliable under deadline pressure, the distribution of automatic versus manual checks has a direct bearing on how often ethical commitments survive contact with production.
A stronger model uses sum types or discriminated unions. A record is either an "identified record with consent" or an "anonymized record without identifiers." The compiler refuses to let you construct the first without proof of consent. This pattern is sometimes called making invalid states unrepresentable, and it is one of the most reliable ways to enforce policy. The rule lives in the type, not in a code review checklist that gets skipped during a crunch.
Languages with algebraic data types and exhaustive pattern matching, such as Haskell, OCaml, F#, Rust, and TypeScript to a meaningful degree, support this style well. Languages with weaker type systems can approximate it with classes, enums, and runtime validation, but the enforcement moves from compile time to test time, which is a weaker guarantee.

Languages that guarantee memory safety by default, through garbage collection or through ownership and borrowing rules, eliminate entire vulnerability classes. Java, C#, Go, Python, and JavaScript are memory safe at the language level, though native extensions can reintroduce the problem. Rust achieves memory safety without a garbage collector through compile-time ownership analysis, which makes it suitable for systems programming where deterministic performance matters.
The ethical argument here is straightforward. If you choose a language that permits buffer overflows and use-after-free bugs, you are accepting a known risk of security failure. That choice may be justified by constraints such as existing codebases, hardware limitations, or team expertise. It is not justified by habit. Teams should be able to articulate why they are accepting memory unsafety and what compensating controls exist, such as fuzzing, sanitizers, and isolated process boundaries.
There is a nuance worth stating plainly. Memory safe languages do not make software secure. They remove one category of defect. Logic errors, injection flaws, authorization bugs, and misconfiguration remain, and they cause plenty of harm. The point is not that Rust or Go solves ethics. The point is that choosing an unsafe language adds risk that must be actively managed, and pretending otherwise is a form of negligence.
Languages that model errors as values, such as Go with its explicit error returns or Rust with its Result type, force the developer to acknowledge failure at every step. This is more verbose, and many developers find it tedious. The benefit is that the failure modes are visible in the type signature. You cannot accidentally ignore an error in Rust without writing code that explicitly does so. In Go, linters catch unchecked errors, and the culture around err handling makes silent failure a visible smell.
Neither approach is universally better. Exceptions reduce boilerplate in code paths where errors are rare and recovery is centralized. Result types shine in systems where every failure needs a deliberate response, such as payment processing or protocol implementations. The ethical question is whether the language makes silent failure easy. If it does, the team needs a strong review culture or tooling to compensate.
Languages differ substantially in how much they help. Languages with a global interpreter lock, such as CPython for CPU bound work, simplify reasoning about shared state at the cost of parallelism. Languages with actor models, such as Erlang and Elixir, isolate state and make message passing the primary coordination mechanism, which eliminates many classes of race. Rust's ownership rules prevent data races at compile time, which is a genuinely rare guarantee. Java and C
Some ecosystems have stronger norms and tooling around this than others. Languages with centralized registries, mandatory lockfiles, and mature auditing tools make it easier to know what you are shipping. Languages with fragmented or permissive publishing models make it easier for a typosquatted package or a compromised maintainer account to reach production. The 2021 Log4Shell vulnerability is a widely cited example of how a single widely used library can create systemic risk across an entire industry, and it happened in a mature ecosystem.
The language influences this in subtle ways. Ecosystems with strong standard libraries reduce dependency sprawl, because teams do not need to pull in a package for every small utility. Ecosystems with weak standard libraries encourage many small dependencies, which increases the attack surface. Languages with strict build reproducibility requirements make it easier to verify what actually got built. Languages with dynamic loading and loose version constraints make it harder.
Practical advice here is to treat dependency policy as an ethical commitment. Pin versions, audit regularly, prefer libraries with clear maintenance and security disclosure processes, and understand the license obligations you are taking on. The language you choose determines how much of this you can automate.
Consider privacy. A rule that forbids logging personally identifiable information can be enforced by a linter that flags calls to logging functions with variables named like email or phone. Such rules exist in several ecosystems and are straightforward to write in others. The difference between a rule that runs on every commit and a rule that lives in a wiki page is the difference between a policy and a hope.
Languages with strong type information give analyzers more to work with. This is one reason why TypeScript, despite being a superset of JavaScript, enables a level of automated checking that plain JavaScript struggles to match. The same pattern appears in Python with type hints and mypy, and in Java and C
Unicode support is a good starting point. Languages and standard libraries that handle Unicode correctly by default reduce the risk of corrupting names, breaking text rendering, or mishandling right-to-left scripts. Languages with weak or inconsistent string handling push this burden onto application code, where it is often done incorrectly. The harm falls disproportionately on users with non-Latin names and on communities whose languages are treated as edge cases.
Date and time handling is another. Time zones, daylight saving transitions, and calendar systems are notoriously error prone. Languages with robust, well-designed date and time libraries, and with type systems that distinguish instants from local times, make it harder to ship software that schedules meetings at the wrong hour or bills customers on the wrong day. This sounds minor until you consider payroll, medication schedules, and international commerce.
The broader point is that a language's standard library is a statement about what the language designers considered important. When accessibility and internationalization are first class, teams inherit good defaults. When they are afterthoughts, teams must build them, and many will not.
First, languages cannot encode most ethical requirements. Whether a system should collect a particular data field, whether an algorithm produces disparate impact, whether a feature manipulates users, and whether a business model is fair are questions that no compiler can answer. They require human judgment, stakeholder input, and often legal and domain expertise.
Second, languages can create a false sense of security. A team using Rust may believe its software is safe because the compiler is strict, and then ship an authorization flaw that has nothing to do with memory. A team using a strong type system may still model the domain incorrectly, and a well-typed wrong model is still wrong.
Third, language choice is often constrained. Legacy systems, hiring markets, platform requirements, and performance needs all limit options. A team maintaining a large PHP codebase cannot simply rewrite it in Haskell. The realistic question is not which language is most ethical in the abstract, but how to mitigate the weaknesses of the language you have.
Fourth, culture dominates. A team with strong review practices, good tests, and a habit of raising concerns can build ethical software in a permissive language. A team with none of those things will find ways to misuse even the safest language. The language sets the floor, not the ceiling.
Audit your language's defaults against your risk profile. List the failure modes your language makes easy, such as null dereferences, unchecked errors, data races, or unsafe memory access. For each, decide whether it is acceptable, and if not, identify the tooling or process that compensates. Write this down. It becomes the basis for onboarding and for architecture reviews.
Encode policy in types and linters wherever possible. If your organization has rules about data handling, consent, logging, or access control, translate them into compile-time checks or automated lint rules. Every rule that runs automatically is a rule that survives a bad week.
Invest in error handling discipline. Choose a convention for how errors propagate, how they are logged, and how they are surfaced to users. Make sure the convention is enforced by tooling, not just documented. Silent failures are one of the most common ways ethical commitments erode in production.
Treat dependencies as part of your codebase. Pin versions, audit regularly, and have a process for responding to disclosed vulnerabilities. Prefer ecosystems with strong tooling for this, and budget time for it.
Choose languages with the constraints of your domain in mind, not just developer preference. A team building safety critical firmware, a team building a public facing web app, and a team building a data pipeline have different risk profiles. The same language is unlikely to be the right answer for all three.
Document the trade-offs you accept. If you are using a memory unsafe language, say why and describe the mitigations. If you are using a dynamically typed language, describe how correctness is ensured. Explicit trade-offs are easier to revisit and easier to defend than implicit ones.
The most useful mindset is not to search for an ethically pure language, because none exists. It is to understand the specific failure modes your language permits, to close the gaps with tooling and culture, and to be honest about the risks you are accepting. Teams that do this build software that harms fewer people, fails more predictably, and earns more trust. That is what ethical development looks like in practice, and the language is where it starts.
all images in this post were generated using AI tools
Category:
Programming LanguagesAuthor:
John Peterson