Skip to content
DSRPT
Dec 25, 2025 · 10 min read

NASA's 10 Coding Rules: How Space-Grade Standards Can Save Your Business Software

NASA’s “Power of 10” coding rules focus on simplicity, predictability, and defensive programming to prevent failures in critical systems. These principles apply directly to business software, helping reduce bugs, downtime, and costly incidents. Companies that prioritize code quality build more reliable, scalable, and maintainable systems.

Abdulkader Safi
Abdulkader Safi Senior Software Engineer
Share:
NASA's 10 Coding Rules: How Space-Grade Standards Can Save Your Business Software

In June 2006, Gerard Holzmann of NASA's Jet Propulsion Laboratory published a three-page paper in IEEE Computer called "The Power of Ten: Rules for Developing Safety Critical Code". Ten rules. That was the whole point.

Holzmann's complaint was that every serious coding standard he had seen ran to hundreds of rules, each new one longer than the last, and that almost none of them could be checked by a tool. So developers ignored them. His fix was a set small enough to memorise and strict enough that a static analyser could prove compliance mechanically.

Below is every rule in full, in Holzmann's own wording, with what it bans, why it exists, and what breaking it looks like in code. The business argument comes at the end, where it belongs.

The 10 rules

Rule 1: simple control flow

Restrict all code to very simple control flow constructs. Do not use goto statements, setjmp or longjmp constructs, and direct or indirect recursion.

The recursion ban surprises people. Holzmann's reason is not style. Without recursion you are guaranteed an acyclic function call graph, which a code analyser can walk to prove that every execution that should terminate does terminate, and to calculate the worst-case stack depth statically.

/* violates rule 1 */
int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

/* complies */
int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n && i <= MAX_N; i++) {
        result *= i;
    }
    return result;
}

The rule does not require a single return point per function. Holzmann explicitly allows an early error return where it is simpler.

Rule 2: every loop gets a fixed upper bound

All loops must have a fixed upper bound. It must be trivially possible for a checking tool to prove statically that a preset upper bound on the number of iterations of a loop cannot be exceeded.

A checking tool has to be able to prove it. A loop you believe terminates does not pass. If the bound cannot be proven statically, the rule is violated.

The suggested pattern for genuinely variable iteration, like walking a linked list, is to add an explicit ceiling anyway, fire an assertion when it is hit, and return an error.

/* violates rule 2 */
while (node != NULL) {
    process(node);
    node = node->next;
}

/* complies */
int i = 0;
while (node != NULL && i < MAX_NODES) {
    process(node);
    node = node->next;
    i++;
}
if (!c_assert(node == NULL)) return ERROR;

Deliberately non-terminating loops, like a process scheduler, get the reverse treatment: you have to statically prove they cannot terminate.

Holzmann and Michael McDougall of GrammaTech later pointed at the Zune 30 as the cleanest public example of this rule being broken. The loop that converted a day count into a date did not handle the 366th day of a leap year and had no failsafe ceiling. Every Zune 30 was bricked for the whole of 31 December 2008. The device was not safety-critical. The same loop in a car or a phone would be.

Rule 3: no dynamic memory allocation after initialisation

Do not use dynamic memory allocation after initialization.

No malloc, no sbrk, no alloca, once the process is running. Allocators and garbage collectors have unpredictable timing, and the whole family of use-after-free, double-free and leak bugs disappears if you never allocate at runtime.

This rule works with Rule 1. With no heap allocation, stack memory is the only thing left that grows, and with no recursion the stack has a provable upper bound. Together they let you prove the program will always fit inside the memory it was given.

This is the rule that translates worst. In a garbage-collected language on a server with elastic memory, pre-allocating everything at startup makes code worse, not safer.

Rule 4: keep functions under about 60 lines

No function should be longer than what can be printed on a single sheet of paper in a standard reference format with one line per statement and one line per declaration. Typically, this means no more than about 60 lines of code per function.

The reasoning is that a function should be one logical unit you can understand and verify on its own, and you cannot do that if it spans four screens. Holzmann treats excessive length as a symptom: long functions are usually a sign of poorly structured code, not of complicated problems.

Note that "60 lines" was a printed page in 2006, not a magic number. The JPL standard made the limit configurable per project.

Rule 5: at least two assertions per function

The assertion density of the code should average to a minimum of two assertions per function. Assertions are used to check for anomalous conditions that should never happen in real-life executions.

Three conditions attached. Assertions must be side-effect free. They must be Boolean tests. And when one fails, an explicit recovery action has to follow, usually returning an error to the caller.

There is an anti-gaming clause: any assertion a static checker can prove will never fail, or never hold, violates the rule. You cannot pad your density with assert(true).

Holzmann's own justification for the density: industrial coding statistics show unit tests find at least one defect per 10 to 100 lines of code written, and the odds of catching those defects rise with assertion density.

int transfer(Account *from, Account *to, long amount) {
    if (!c_assert(from != NULL && to != NULL)) return ERROR;
    if (!c_assert(amount > 0)) return ERROR;
    ...
}

The JPL version later loosened this to two assertions per function longer than 20 lines, because tiny functions could not meet it sensibly.

Rule 6: declare data at the smallest possible scope

Data objects must be declared at the smallest possible level of scope.

Data hiding, stated as a checkable rule. If an object is not in scope, nothing can corrupt it. And when you do have to diagnose a bad value, the number of statements that could have written it is the size of your search space.

It also discourages reusing one variable for several unrelated purposes, which is a reliable way to make a fault impossible to trace.

Rule 7: check every return value and every parameter

The return value of non-void functions must be checked by each calling function, and the validity of parameters must be checked inside each function.

Holzmann calls this the most frequently violated rule and admits it is the most suspect as a blanket rule. In its strictest form it means checking the return of printf and close.

His concession: if your response to failure would be identical to your response to success, cast the return to (void) so the intent is explicit rather than accidental. In less obvious cases, leave a comment explaining why the value is irrelevant.

/* violates rule 7 */
fclose(fp);

/* complies, and says so on purpose */
(void) fclose(fp);

He notes the C standard library breaks this rule itself, with consequences: try strlen(0) and see what happens.

Rule 8: limit the preprocessor to includes and simple macros

The use of the preprocessor must be limited to the inclusion of header files and simple macro definitions.

Banned: token pasting, variable argument lists, recursive macro calls. Every macro has to expand into a complete syntactic unit. Conditional compilation is allowed but discouraged beyond include guards.

The arithmetic behind that last part is the strongest argument in the paper. Ten conditional compilation directives produce up to 2^10 possible versions of the code, and every one of them would have to be tested.

/* violates rule 8 */
#define LOOP_START(x) for (int i = 0; i < x; i++) {

/* complies */
#define MAX_NODES 1024

Rule 9: limit pointer dereferencing to one level

The use of pointers should be restricted. Specifically, no more than one level of dereferencing is allowed. Pointer dereference operations may not be hidden in macro definitions or inside typedef declarations. Function pointers are not permitted.

Hidden dereferences are as much the target as deep ones. A dereference buried in a typedef defeats both the human reader and the analyser.

Function pointers were the sticking point. If a tool cannot tell which function a pointer resolves to, it cannot prove the absence of recursion, and Rule 1 loses its teeth. In practice the total ban proved too strict, especially against legacy code, so it was relaxed: function pointers are allowed where it stays tractable which function is being pointed at, if not to a human then at least to a static analyser.

Rule 10: zero warnings, from day one

All code must be compiled, from the first day of development, with all compiler warnings enabled at the compiler's most pedantic setting. All code must compile with these settings without any warnings.

Plus daily checks with at least one, preferably more than one, static source code analyser, also at zero warnings.

The part most people skip is what happens when the warning is wrong. Holzmann's answer: rewrite the code anyway. If the compiler or the analyser gets confused, the code that confused it should be made more trivially valid. His reason is experience. Developers have repeatedly assumed a warning was invalid and found out much later that it was right for a reason they had not seen.

The Power of Ten rules and JPL's Mars missions

The rules were not written and shelved. A small JPL team building mission-critical flight modules tested them first, using manual review plus a prototype of GrammaTech's CodeSonar static analyser. The finding was that you could work inside the rules and still hit a real flight project's schedule.

That led to the JPL Institutional Coding Standard for the C Programming Language, released in March 2009, which combined the Power of Ten with MISRA-C 2004 and a set of JPL-specific rules covering how tasks in a real-time system interact. Complex task interaction had caused a long list of spacecraft bugs, including the priority-inversion fault on the Mars Pathfinder lander.

The standard applies to all new flight software development at JPL. All flight software for the Mars Science Laboratory mission, the one that landed Curiosity, was written to comply with it. That rover runs more code than every previous Mars mission combined, a few million lines of C.

Two details from the JPL rollout are worth more than the rules themselves.

The first is compliance levels. The standard splits into six, from LOC-1 (language compliance) up to LOC-6 (MISRA "should" rules), and a project picks the level that matches how critical it is. Holzmann and McDougall are direct that this is what got the standard adopted: quality improvement is not all or nothing, and a gradual path was what made both management and developers say yes.

The second is what they found when they asked JPL's own developers to name the 10 most important and 10 least important MISRA-C rules. The developers largely agreed with each other. But there was little or no correlation between the rules they said mattered most and the practices they actually followed in their own code. Knowing the right thing and doing it are separate problems, which is exactly why the rules were designed to be enforced by a tool.

JPL now runs static analysis on every build of flight code and feeds the results into a peer review tool called Scrub.

Which of these rules survive outside C

Holzmann and McDougall address this directly, and their answer is more modest than the internet version of it.

Rules that carry over to any language with no modification: bounded loops, short functions, minimal scope, checking return values and parameters, and zero warnings. Rule 2 in particular applies to virtually every language in current use, and the failure it prevents (the operation that runs until a flag flips, and the flag never flips) is the same failure in a queue consumer, a retry loop, an API poller or a batch job.

Rules that are C-specific: 3 (no heap allocation), 8 (preprocessor), and 9 (pointer dereferencing). Their underlying ideas travel, but the letter of the rule does not. "Don't allocate at runtime" becomes "know how your system behaves under load before production tells you". "Limit dereferencing" becomes "stop chaining order.customer.address.city.zipCode". These are useful reframings, not the rules.

Rule 5 is the awkward one. Two assertions per function is a C convention. The equivalent in most modern codebases is input validation at the boundaries plus real tests, not assert calls sprinkled through business logic.

If your project does not warrant a standard this strict, the authors' own advice is to keep the general principles and drop the specific numbers. Sixty lines may be wrong for your codebase. Short functions are not.

What this actually costs businesses

The credible number, since a lot of articles get it wrong: CISQ put the cost of poor software quality in the United States at $2.41 trillion for 2022, with about $1.52 trillion of that being accumulated technical debt. That figure is the US only, not worldwide.

Most of what the Power of Ten prevents is not exotic. It is the unbounded loop that keeps consuming, the return value nobody checked so a write silently failed, the warning that sat in a pile of 300 others. None of those need a spacecraft to hurt.

The practical version for a normal engineering team is three changes, in order of how cheap they are:

  1. Turn on every compiler and linter warning, fix or explicitly waive each one, then fail the build on new warnings. This is Rule 10 and it costs a day.
  2. Put a ceiling on every loop, retry, poll and queue consumer that currently runs "until done". This is Rule 2 and it is the rule with the highest ratio of damage prevented to effort spent.
  3. Check return values on the paths where failure is silent: file writes, external API calls, database updates, auth checks. This is Rule 7.

Function length limits and scope rules come after those, because linters can enforce them and nobody has to remember. That is the real lesson from JPL: the rules that worked were the ones a tool checked on every build, not the ones everyone agreed with.

If you want the fuller picture on where quality problems actually surface, we covered the testing side in Quality assurance: why test in production costs you and the detection side in Performance monitoring: stop hearing about outages from customers.

There is also a newer wrinkle. When code is generated rather than written, the check that used to happen in someone's head does not happen at all, which makes the tool-enforced rules the only ones left. We looked at that in Bun's Rust rewrite passed every test and no human read the code.

Sources


Build software that holds up

DSRPT builds and audits software for businesses across Kuwait, the GCC and Australia. If you want a read on where your codebase is fragile before production tells you, get in touch.

NEWSLETTER

Stay Ahead of the Curve

Get the latest digital marketing insights delivered to your inbox weekly.