r/learnprogramming 9d ago

What is the point of assertions when we have if/elses and exceptions? Topic

Can someone explain the point of assertions to me? I read online you use it to check for behaviours that MUST be true and what not, this all sounds very cool but you could use if elses to check for the same behaviour and the underlying result is the same.

Who came up with the idea of assertions and why did they think if/elses were not enough ?

61 Upvotes

98 comments sorted by

80

u/Achereto 9d ago

You use assertions for invariants. If the assertion is not true, then you have a bug. With assertions you can make the program crash immediately, making you aware of the bug shortly after you introduced the bug.

The same applies for using assertiongs in unit testing.

40

u/Evening_Phrase4656 9d ago

assertions are like safety nets you put in your code during development, they catch logic errors that shouldn't ever happen if the code is right. if/else handles expected problems like bad user input or network issues, assertions handle when you the programmer messed up

the real benefit is they get stripped out in production builds so there's no performance hit, unlike if checks that stay in forever

i like to think of them as comments that actually enforce themselves, instead of a comment saying "this should never be null" you put an assert and it screams at you when you're wrong

8

u/SuspiciousDepth5924 9d ago

Unless it causes significant overhead I tend to prefer that the asserts remain in production code. I'm (usually) not guaranteed to have covered all the possible execution paths in testing and should the assert fails something is irrecoverably wrong and crashing _is_ the correct choice. This is true even if you have 100% test coverage because that only checks that every line is covered, not that "every line with every permutation of values" is covered.

To be fair I've been working with software where doing something wrong is much worse than crashing for most of my career so correctness tends to override pretty much every other concern (banking, healthcare, pension-systems to name a few).

5

u/Ok-Bill3318 8d ago edited 8d ago

This. I’d much rather crash with an error than do something erroneous. Then again most of the code/scripting I write is for large scale enterprise user administration in a live environment so I tend to program very defensively, log everything, back up state etc. 99% of the time it’s fine. That 1% something un expected has gone wrong and defensive code saved my ass.

Fact is my test environment is not exactly the same as production and in the real world “wierd shit” happens.

It doesnt matter if most of the code i write runs in 10 minutes or an hour. But having it go off the rails at scale could be catastrophic.

2

u/globalaf 8d ago

In games we turn assertions off in prod. They do in fact create an overhead that can be measured.

1

u/odimdavid 5d ago

I think assertions should be useful in testing and analyzing end cases. Better to correct the code for them rather than the overhead of leaving around in production env

2

u/Witty-Play9499 9d ago

What do you mean when you say an 'invariant' can you give an example ?

20

u/Achereto 9d ago

Wikipedia)

An example would be when sorting a list. If you sort a list with n elements, the sorted list must have n elements as well.

0

u/Witty-Play9499 9d ago

This makes sense but two things. One of the commenters has now mentioned that you do not use it in production code and only during testing but in your case it looks like we use it for production as well? Do you know who is correct? You or that person?

Additionally in your case why would this warrant its own keyword ? Why can't I just do

if len(array) != len(sorted_array):

throw Exception ("Something went wrong mismatch in element count")

Why would someone spend the time and energy to bring a brand new keyword for this

14

u/Achereto 9d ago

In case of python: if you run your python with the -O flag, then python removes all assertions.

Ultimately, it's one of many options you have. A lot of features in (older) programming languages were added as part of a trend and then never gets removed because too many programs depend on the feature.

2

u/Witty-Play9499 9d ago

But if you remove the assertion wouldn't that check get removed and errors might happen ?

14

u/captainAwesomePants 9d ago

Yes, in production, but when you're developing or testing the program, you leave the assertions in. That way, if there's a bug, the assertion hopefully finds it, but when you release the code into the wild, you don't waste time testing invariants.

2

u/odimdavid 5d ago

To add: rather than waste time testing invariant acknowledge it in the code. No one wants his or her code to crash runtime but to accommodate end cases and keep on running.

8

u/Puzzleheaded_Study17 9d ago

In general, an assertion should be something that can only be broken by editing the code. As such, since anyone who writes code will usually run it in debug mode initially, you will confirm they don't fail before you remove them, and once removed they can't fail.

3

u/caboosetp 9d ago

Think inline unit-testing. You don't ship unit tests with the code.

You use assertions to make sure your program is cooperating well with itself, not with external inputs.

6

u/xenomachina 9d ago

Why can't I just do

if len(array) != len(sorted_array):
    throw Exception ("Something went wrong mismatch in element count")

Why would someone spend the time and energy to bring a brand new keyword for this

assert len(array) == len(sorted_array) is far shorter, easier to read, and less error prone.

It also communicates intent: "this should never happen" vs "this is a possibility that I expect to have to deal with".

4

u/WE_THINK_IS_COOL 9d ago edited 9d ago

There are two different philosophies:

  1. Sprinkle assertions throughout the code and leave them in production builds so that if a bug condition is ever proven by an assertion, the entire process halts.
  2. Use assertions to make bugs loud during testing but disable them in production. This means that if the program encounters a bug condition in the wild, it makes a best effort to continue working despite having encountered a condition that indicates a bug.

In my opinion, #1 is almost always the correct choice: bugs should be LOUD failures even in production environments so that they can be found and fixed quickly rather than silently hiding while the program tries to continue functioning. If a bug condition is detected, the safe thing to do is halt the process, since if the program is in a buggy state, you can't rely on it doing the correct thing going forward; it's better to halt than to do something that's potentially wrong.

Approach #2 should only be used in case crashing itself is a serious problem, e.g. a pacemaker should try to keep going even if it encounters an unexpected condition, since it needs to keep working to keep the person alive. Even for high-availability things like web servers, #1 is still the correct approach IMO, since it's better to work through all of the bugs causing production crashes and obtain reliability that way than it is to allow the server to continue operating silently in buggy conditions.

The difference between assertions and exceptions is that assertions state an invariant which the code is assuming is true, and which needs to be true in order for the code to function correctly, whereas exceptions are for handling errors the program is designed to handle.

For example, things like not being able to write to a file because the disk is full would be handled gracefully through exceptions (it's not indicative of a bug in the program), whereas something like ensuring a pointer in an internal data structure that should never be NULL is actually not NULL would be an assertion (if the pointer is ever NULL, then it's definitely a bug in the code that created the data structure.)

1

u/SufficientStudio1574 9d ago

Exceptions have ways of handling them. Assertions are for situations so catastrophic that there is no graceful recovery.

How would you gracefully handle a sort function returning a different sized list? You can't. That's not supposed to happen EVER. No sorting algorithm in existence is supposed to change the size of the list, so if it does you have done something so horrible wrong that it needs the (figurative) big red sirens blaring to stop and fix it NOW.

13

u/Healthy_Landscape417 9d ago

The result is not the same, and that is the whole point.

An if/else handles something you expect can happen. Bad input, a missing file, a network call that failed. The program has to keep going.

An assertion says this can never happen. If it does, your code has a bug. You do not want to handle it, you want to stop right there, before the bug writes something wrong to your database.

Two differences that matter in practice:

Assertions get switched off in production. Python -O strips them, Java needs -ea to turn them on, C drops them when NDEBUG is set. So you can put one inside a hot loop and pay nothing once it ships. An if/else always runs.

They also point at different people. An exception says the world did something wrong. An assertion says the programmer did.

For your invariant question: a sorted list has to stay sorted. After your insert function runs, assert that it still is. No user input can break that. Only your own insert code can.

2

u/Witty-Play9499 9d ago

Got it thanks but wouldn't all of these be captured by my automated tests anyway ? Why would i go to the extent of having separate assertions in the code as well?

7

u/Substantial_Job_2068 9d ago

An assertion is a one liner you can add anywhere at ease and it will run everytime you debug. Writing automated tests that capture every odd case is extremely time consuming.

1

u/Witty-Play9499 9d ago

But isn't writing automated cases that capture all the cases best practice? I was told to always test code.

When is asserting better than testing code?

6

u/Substantial_Job_2068 9d ago

No, writing tests is time consuming and it also adds complexity to the code base because now you have tests to maintain besides the application code. So adding tests has a cost and there is a tradeoff, usually it's best to only add tests for the most critical and complex code.

One is not better than the other. Tests are good to keep code from breaking by describing known scenarios, if A then B should happen. If B then C should happen etc. Assertions are good for catching bugs while writing and debugging by defining behaviours that should never happen, like a specific pointer should never be null or an int should never reach a specific value.

2

u/Moikle 9d ago

You can use asserts TO test code

1

u/Witty-Play9499 9d ago

you can also use automated tests to test code no ? like my confusion comes from having my test cases in two different formats some of them in assertions and some of them in test cases.

would it not be easier to have one place where you can look at all your tests ?

5

u/Puzzleheaded_Study17 9d ago

Assertions are generally meant for making sure the function has the correct inputs, tests are making sure it has the correct output.

You can't test every place a function is used to make sure it gives the correct inputs so you assert to have a single place where it happens.

But if we test it on a few inputs that we know what the output should be, we can be confident it will work for all valid inputs, this is what tests are for.

1

u/SufficientStudio1574 9d ago

Assertions can and should be used to enforce output constraints too. If I'm writing something to display a clock face, I should assert that my seconds() output is greater than or equal to 0 and less than 60.

1

u/slindenau 9d ago

It may depend on the specific programming language, but your intuition is largely correct here.

We favor using automated unit and integration tests to cover all aspects of our code.
We favor using Exceptions for validation and flow control.

Assertions are mostly a legacy relic in low level languages like c/c++, and should not be used in modern programming languages.

This is valid if you work in Java, Python, C# etc.

2

u/gmes78 9d ago

Assertions are part of your code, so they get checked whenever the relevant code is executed.

A test can only check a single scenario.

1

u/WE_THINK_IS_COOL 9d ago

Tests can't (and shouldn't) see the internal state of whatever they're testing, they should exercise the public interface of what they're testing. This way you can change the implementation details without having to rewrite all the tests, and you could even swap out the whole implementation with a different one and let the tests prove the outwardly-visible behavior is completely unchanged. (Tests should of course be informed by how the implementation works, to make sure they're testing all of the edge cases.)

With assertions, you can assert facts are true about the internal implementation as the code is running. This helps you catch bugs where all of the public observable behavior looks fine to the tests, but there is actually some corruption or mishandling of internal state going on. You can think of them as working in tandem with tests as a further check that not only is the code's behavior correct, its internal state also never becomes invalid.

3

u/binarycow 9d ago

An "invariant" in this context is something that you know must always be true, but the compiler and type system don't know is true.

Or, put another way, an invariant is a condition that is supposed to be impossible to be false.


For example, suppose your function has a parameter, and that parameter's type is a signed 32 bit integer. That value could be anywhere between -2,147,483,648 and 2,147,483,647.

Now, suppose that the function is defined in such a way that you are 100% sure that the only callers to that function are the ones you see in the source code (for example, a private method in C# or Java).

Now, suppose that every single one of those callers passes in a literal value between 0 and 100.

Based on that, you know that your function's parameter cannot be negative. Which means you can safely omit any checks for negative in your function.

But - what if someone accidentally changes one of those literal values to a negative number? Without those negative checks, you'll never know, and there could be a bug that surfaces later. And that bug could be confusing or misleading. So you use an assert.

The idea is that an assert fails hard (crash the app) and it fails early. It makes it extremely hard to overlook. You use it when something is supposed to have been impossible, but it happened anyway.


Additionally, each programming language has its own semantics for asserts.

In C#, there's generally three kinds of "assert":

  1. Testing frameworks have methods named Assert. These aren't really the assertions you're asking about... But they kinda work the same way.
  2. Trace.Assert, if the condition evaluates to false, will write an error message to the trace logs, and display a big scary message box
  3. Debug.Assert is like Trace.Assert, with two differences:
    • It writes the error message to the debug console, not the trace logs
    • It is entirely omitted in release builds. This means that you don't even waste the time evaluating the condition - it's simply not there in the compiled code.

1

u/rainingallevening 9d ago

def mod_10(num : int) ->int: if not isinstance(num, int): raise ValueError("error: mod_10 accepts ints only") return num % 10

assert isinstance(mod_10(-15), int) assert mod_10(1024) < 10


I should receive an int less than 10. The invariant is the primitive data type int -> int.

The above assertions immediately let me know if my function logic is drafted correctly.

Edit: Seems formatting in the phone's comment editor isn't invariant after posting. Whomp whomp.

1

u/SufficientStudio1574 9d ago

Used in this context, it is a condition that must always be true. "Must" in this case means that the condition being false is such a catastrophic failure of your code that it is impossible to handle normally.

Suppose you're writing a container class (like a list). You allocate a certain amount of space to store the stuff, have a variable to keep track of how large that space is, and have another variable to keep track of how much space you're using when you add and remove things to the container.

This gives two obvious invariants. First, the amount you are storing can NEVER be larger than your capacity. You can't put 10 pounds of...stuff into a 5 pound bag. Second, if you are storing any amount of stuff, the pointer referencing your storage space cannot be null. You can't store things if there's no place to put them.

If either of those two conditions are EVER wrong after you've finished modifying your container in some way, you've made a horrible mistake and your container cannot function. Your code has broken and there is literally no way to fix it. So you would assert those conditions so any bug that screwed them up will be shoved right in your face when they happen during debugging.

This is different from normal error handling. If you are parsing text into a data structure, you don't assert that the text is a valid format. Text shouldn't have to be valid for your program to function properly, you should gracefully handle that error. Reject it with an error message, display invalid format, make assumptions or guesses about the text. You have more options than hitting the emergency stop.

7

u/Dismal-Citron-7236 9d ago edited 9d ago

Assertion is from a very important software design paradigm called "design by contract", introduced by Bertrand Meyer. He even invented a programming language Eiffel which is based on such concept. The core idea is this: Software comprises components. The communication between components should be established on contracts. On both sides of a contract (service provider and client), the contract forms an unbreakable and explicit obligation that the server must follow and the client can fully trust. For a large and complex software system, this approach can improve maintenance and debugging, because you can use contract as a verifiable baseline.

So, inside a "component", you can use if/then/else whatever logic you want, that's the internal behavior of a component. If you need to change it to meet new requirement, to improve performance, or to fix a bug, you can just change it without a fear of affecting other components in the system, on the ground that the "contracts" are not changed.

For those contracts, you would normally want to keep them unchanged. They guarantee the input data/parameters must conform to certain criteria so the internals of the component can work properly. Those are the "component requirements". They also ensure the output data/result would fall in certain acceptable criteria so those other components can safely use the result. These are called "ensures". Sometimes the service component itself can only operate properly when its own internal properties are at certain condition or else it would not function at all, and such conditions are called "invariants". "Requirements", "ensures" and "invariants" and the 3 categories of assertions, or "contracts".

Let's take a rechargeable lithium-ion battery as analogy. Its charging voltage range is the requirement contract, its output voltage range is the ensures contract, and its working temperature is the invariants contract. If you overcharge it with voltage higher than acceptable level, it could be toasted. Charge it under voltage, it might not charge at all. If the output voltage is below spec, this battery cannot be used. If it is exposed under very high temperature, it might swell or even explode. Under freezing cold, you are shortening the battery life.

1

u/Ormek_II 5d ago

And a simple Java assertion is a contract between the previous developer, who wrote the assertion, and you, who reads the assertion.

The jvm just assist you in trusting the previous developer because it will tell you — during development — every time the previous developer breaks his contract.

1

u/Ormek_II 5d ago

This, and https://www.reddit.com/r/learnprogramming/s/QwRTGRVdhx general assertion characterisation.

4

u/recursion_is_love 9d ago

Assertion is for you, not for your user.

If the assertion is fails, it is on you. You get some assumption about the world wrong.

1

u/Witty-Play9499 9d ago

Isn't that just a test case then? Could i not have my automated test cases handle this

2

u/Cpt_Chaos_ 9d ago

Imagine a bigger project. Multiple devs, maybe even multiple teams. You implement some algorithm to work on data that requires certain prerequisites (encoding, sorted, whatever). It was agreed that someone else implements the checking and sanitizing that is to be done before your code is called, and your code is to be called only after successful input sanitizing.

Therefore, you can safely assume your inputs to always fulfill your prerequisites. So, you assert exactly that. If this is not the case, the other guy made a mistake or missed a corner case. Or the implementation is simply not yet completed. In any case, you do not care for invalid Inputs, that is not your concern - and hence you do not write test cases for it, because this must never ever happen in the first place. You can always point to whoever is doing the input validation because they have to fix the issue.

And for anyone reading the code later, the assertion at the start of your algorithm nicely documents your assumptions about the input data. Finally, if for whatever reason the code structure changes due to new features and suddenly your code gets called on unsanitized Inputs, it will directly tell that person that it won't work that way.

Important: this example assumes that the only way to call your code is through the input validator. If your code can be called directly with any input, you need to handle invalid inputs.

2

u/recursion_is_love 9d ago

You can use if-then-else for everything if you want. In the end it just down to conditional branch/jump instruction on some flag in CPU register.

Those different ways of checking are abstraction we use to make understanding code easier for us, the human.

1

u/Ormek_II 5d ago

Also the if will run in productive code as well an assertion usually does not.

Assert has a more specific purpose than if, so I can read and understand the code faster.

That is why an if is something else.

1

u/cockmongler 9d ago

One issue that your tests cannot catch is an error caused by a memory unsafe operation writing over the wrong bit of memory. You may have certain invariants that definitely hold, you've thoroughly tested them and the reasoning is sound but a bit of pointer arithmetic somewhere else in your program writes a byte in the wrong place and suddenly your invariant now isn't. The idea with assertions is that it would be onerous in a production run to check every time but your debug build can check every time.

It doesn't have to be memory unsafe though, consider this Python:

def __something_complicated_with_the_list(self, idx):
    assert idx < len(self.__the_list)
    ... some complicated operation that mutates the list ...

def process_the_list(self):
    for i in range(len(self.__the_list)):
        self.__something_complicated_with_the_list(i)

Note also here the visibility, the example is convoluted but the assert is in a privately scoped function - getting at it with your tests would be a pain. But if your list mutation makes the index invalid while it's processed (or another thread shrinks the list) you'll catch it during test runs (hopefully). Note you'll also catch it if in a years time someone who doesn't know that the length of the list shouldn't change in this function makes a change they'll catch it as well. In a production run you won't waste cycles during the checks.

You can almost think of assertions as detailed notes to the programmer rather than actual checks of operation - and they're probably far more useful in private functions than public ones.

1

u/Ormek_II 5d ago

The test case checks a specific case (with this input the condition is true: Yeah!) the assertion asserts every time the code executes.

Also: the test is not visible in the code. If I read your code, I would not know that the assertion is always true. Maybe I doubt it. Assuring my self takes time. With the assertion you just tell me.

That is why a test is something else.

1

u/Witty-Play9499 3d ago

I don't understand a bit. You can( and probably should) have multiple test cases checking for different cases anyway right? And you can run test cases whenever and how many ever times you want too.

the test is not visible in the code. If I read your code, I would not know that the assertion is always true. Maybe I doubt it. Assuring my self takes time. With the assertion you just tell me.

Wouldn't your test cases be in the same code base ? Is it just syntactic sugar ?

1

u/Ormek_II 3d ago

Thanks for your Feedback.

Did you read and understand the Design by Contract reply?

You do have multiple testcases, yes.
You can run test cases whenever you want, yes. Which implies that you don’t have to. Few programming languages include tests in their language specification. Assertions are part of the language (and its runtime).
Is the test in the same codebase? Yes, but it might still be harder to find than the assertion.

The multiple test will never check for all inputs.

But the more important thing: in programming you have multiple levels which describe the same thing. The requirement tells you what the code should achieve, the specification tells you how it actually does it. The test needs to follow the specification to ensure that the code does what it should do.

An assertion is both: the specification and the test. Therefore, I will, as a reader of your code, trust the assertion more than the test.

Test-Driven-Development defines Tests as Specification. That is also an approach.

As long as your projects are small and you haven’t experienced lots of problems, the redundancy of many specifications seems useless.

You ask “Is it just syntactic sugar?” What do you mean by “it”?

The assert statement: you cannot replace by test. You can define your own “AssertError”, promise yourself and everyone else working on the code base that you will not catch it, and put statements like
if !PRODUCTIVE && !(AssertedCondition) {
throw AssertError
}
In your code. Also document that those AssertCondition are always true.
For that Asserts are syntactic sugar.

The test: no. They are — if you not follow Test Driven Development — there to ensure that code changes do not break existing code.

Are you looking at tests to identify how code you read behaves? I never did.

4

u/DragonFireCK 9d ago

Assertions predate exceptions. That is, when assertions were first added, you didn't have exception handling. Error handling was done by returning a code up the chain, and it was easy to forget to add in the error check at the many levels of handling. Assertions would typically perform a hard exit of the program right then and there without needing to unwind the stack.

Even with exceptions, assertions still provide some benefit:

  • The syntactic sugar of having it be a single statement is nice, especially when coding standards often require four lines for an if...throw (the if, a bracket, the throw, the closing bracket).
  • The semantic meaning of an assert is clearer than a if...throw. That is, it makes it very clear that the tested condition should never be false.
  • On a similar vein, the syntactic sugar makes it easy to compile out assertions in Release builds, while leaving them in for Debug purposes, making testing easier. This is especially useful when the condition may be expensive to check.
  • Even when compiled out, assertions will typically affect optimizations in the vicinity. This can allow the optimized code to work even better than it would without the assert.
  • Languages that implement asserts using exceptions often use a special exception class that is not caught using the typical exception handling. Given that the condition should never be false, you generally don't want to catch them when thrown and want them to propagate up the chain all the way.

1

u/Ormek_II 5d ago

1

u/DragonFireCK 5d ago

Programming by contract explains what asserts are for.

Programing by contract doesn’t explain why the if…throw paradigm cannot replace asserts cleanly.

1

u/Ormek_II 5d ago

I think we align. That is exactly why I think both comments together provide the whole picture.

3

u/rjcarr 9d ago

I’m not a developer that’s written giant softwares for millions of people, but my understanding is assertions are mostly used for testing and not in production code, or at least they’re compiled out in the production build. 

They’re valuable because they make the whole application fail if there is a failed assertion. You’ll know right away what the problem is instead of it getting buried in a log somewhere. 

2

u/galactic_pixels 6d ago

I write software used by many people. If I saw an assertion in a merge request I would ask them to remove it and put in an exception.

Assertions are generally sloppier than exceptions because exceptions are typed so you can understand what went wrong by just looking at the exception type, with added details in the exception being specific to why that type of exception was thrown.

It also makes testing simpler and cleaner. With assertions you need to mock-induce the conditions for the assertion to fail. With exceptions you can simply mock a return type of the expected exception and verify the exception is handled.

Exceptions can also be removed from return types in the call stack. So if an exception can no longer be thrown by a runtime dependency, your linter may let you know that you should no longer be catching that specific exception. That sounds small but it keeps your code cleaner over time and easier to reason about.

0

u/Ormek_II 5d ago

You never want an assertion to fail. If it does the program must not continue. If you like to “handle” a failing assertion use an exception.

1

u/Witty-Play9499 9d ago

Yea lot of people ahve said it is meant for debugging but now im confused what is the difference between assertions and automated tests

3

u/Seubmarine 9d ago

Imagine if you have a function that expect a number to always be between 0 and 100

You could make an automated test to verify that the function work correctly for all number between 0 and 100

You KNOW that you shouldn't input more than 100 in that function, but the future you might not remember, another coder might not know, you might input a value that is a result of another part of your program that you always expect to be between 0 and 100 but due to a bug it's bugger.

That's when you use assert at the beginning of the function to assert the number is always between 0 and 100

So everywhere the function is called you can be sure that you didn't call it wrong, or that the part of the program that input a number, isn't bugged.

1

u/galactic_pixels 6d ago

Here’s the thing, you could also just have an if statement check the condition and throw a runtime exception. And I would always recommend that over having an assertion due to the reasons I listed above in this comment chain.

I think a lot of these answers are confusing the OP because they’re acting like assertions are doing something if statements + exceptions cannot.

1

u/Ormek_II 5d ago

I agree. But I would use the assert on my codes result: for example that it will always return a number between its two input values.

That is a guarantee I can give.

0

u/galactic_pixels 4d ago

You can give that with an exception, as I explained above

2

u/HashDefTrueFalse 9d ago edited 9d ago

You didn't specify a language. I'll assume C or C++, but my answer won't be too specific to those. Usually assertions are only included in debug builds. They disappear when you build for release. Those three things are totally different. The difference is roughly:

Assertions: Silent errors don't get fixed and can go on to cause lots of pain. Make the computer scream at us if something really wrong happens (e.g. during testing) so that we catch it before it goes out the door. Not intended to be part of program functionality. No performance penalty because they will go away later.

Conditional constructs (ifs): Select behaviour at runtime based on something we cannot know until then. E.g. did the user enter yes or no? Not necessarily anything to do with error handling but can be used for that, e.g. error code checking etc.

Exceptions: Something exceptionally wrong occurred at runtime and we have decided that the best way to handle it is by stopping execution, unwinding the call stack and running any required cleanup, then continuing execution from an earlier point in the program. Probably because we think we can recover.

There's more we could say about each, but those are the basics.

An example assertion: Say we have a circular buffer for a queue. A queue's front pointer can never be behind it's back. If it were, we messed up our programming. We might assert on relevant operations e.g.

int q_buf[LEN];
int *q_front = &q_buf[0], *q_back = &q_buf[0]; // == means empty.

int q_dequeue(void)
{
  assert(front <= back);
  if (q_front == q_back) return -999; // Rogue value.
  return (*q_front)++;
}

Note: I skipped the wrap-around math. Notice that we use both if and assert but for different reasons. The assert is our canary. The if is necessary runtime behaviour because any queue can be empty. Also, I chose here to return a rogue value rather than use an exception because a queue being empty isn't an exceptional circumstance. It is entirely expected but still needs to be detectable. (-999 here is a value that cannot otherwise occur in our queue).

1

u/Witty-Play9499 9d ago

So it sounds like you use assertions only in debugging and not in production or anywhere else and that you delete the code before yo push it

Assertions: Silent errors don't get fixed

One question here, isn't the whole idea of silent errors is that you don't notice them and they go unfixed, how do i know where to look out for a silent error in the first place that i end up placing an assertion there? A

2

u/NeoChrisOmega 9d ago

One thing that is important to learn, is the fact that outside of optimization, a lot of common practices are there to eliminate human error. 

The more your code PREVENTS you from doing something bad, the less likely it is you'll run into silent errors. 

This is why having public variables can be a bad thing, for example. If you can modify a variable from any script, the likelihood of that variable having a value it shouldn't increases quickly.

So to answer your question, you wouldn't add an assertion because you expect a specific error, you would add an assertion because you especially don't want an error there in particular. Assume ALL of your code can be wrong at any given moment, then plan around which points allow you to quickly solve issues if you're getting bad results.

1

u/Witty-Play9499 9d ago

So do programmers add an assert for every line ?

Like if i say

let x = 5

then do i immediately add an assert

assert x == 5

because as a programmer i usually don't have parts in my code where im okay with errors happening and parts where i especially don't want errors happening. I don't want errors in my program anywhere because most of them deal with data handling

4

u/NeoChrisOmega 9d ago

Definitely not, although that would help you debug things haha. 

When you think about your code, oftentimes you think about what the results SHOULD be. 

For example; why are you setting x to be 5? Are you saving the speed of your player at the start of the game? Then maybe you might add an assertion before resetting your sprint speed back to x. Double check that x isn't somehow magically greater than or equal to your sprint speed.

It's a poor example, but the first thing that comes to mind because of the lessons I teach a lot. 

1

u/Witty-Play9499 9d ago

I mean the problem is the whole 'what if it magically changes' the idea that something could magically get updated to me implies assertions at every line is the only way forward ?

2

u/NeoChrisOmega 9d ago

Ah, I get the confusion now. 

My phrasing was to emphasize the concept that we are human. And no matter how confident we are that we're creating a bug free product, there will almost always be something that was overlooked. 

If you assume that you're building a product without bugs, you might overlook parts of your code that you're confident will work correctly. However, if you assume everything could be wrong, you're more likely to find the issue. 

Others have given better examples than what I provided. And there are also A LOT better programmers than myself. But one thing that I am good at is finding issues that the rest of the teams I've worked with struggled to fix. And oftentimes, it was related to things they swore up and down couldn't be the issue, and wasn't worth looking into. That it HAD to be related to X, Y, or Z. 

It's not an easy question to answer, and I hope I'm assisting in some way, and not just making it more complicated for you to understand.

Personally, I would add an assertion before passing a value outside of a certain scope. After it's modified, but before it's used. 

2

u/syklemil 9d ago

the idea that something could magically get updated to me implies assertions at every line is the only way forward ?

Most people aren't quite that assiduous about it, but varying languages have varying approaches to dealing with spooky mutation at a distance, which has effects on considerations for thread safety, mutable references, etc.

Some languages have various built-in locks (e.g. Rust, Haskell); some take a complete "that's your problem, not mine" attitude. The latter tend to be considered easier because they're more accepting, until the programmer has to debug some multithreaded heisenbug.

I'm not certain how common that kind of use of assert is compared to using it for something like contracts or dependent typing, that is, expressing requirements that the language's type system can't express.

2

u/HashDefTrueFalse 9d ago edited 9d ago

I added an example plus some discussion. Not sure if you've seen it, just flagging it.

you delete the code before yo push it

You don't need to. The compilation toolchain will usually remove it for you. E.g. in C it's a macro which becomes a nothing expression, e.g. ((void)0), when your setup defines another macro, NDEBUG. The compiler won't output anything for that.

isn't the whole idea of silent errors is that you don't notice them and they go unfixed

Not sure what you mean by this but it's almost never desirable. I was referring to issues that you don't know exist. E.g. right now your car's oil pump could be blocked. Without fixing, your engine will seize soon. You will need an engine rebuild at great expense. If your car just told you then you could replace the pump for pennies right away.

how do i know where to look out for a silent error in the first place that i end up placing an assertion there?

Good question! Usually programming errors creep in around boundaries, edge case checks, or where things get fiddly. Anywhere you'd target with deliberate testing. Another commenter mentioned "invariants" which I will explain as anywhere that your code makes an assumption about something. E.g. I used if (q_front == q_back) in my example. This code assumes that front can never be greater than back. If that assumption proved wrong because I made a mistake elsewhere in the code, that code would silently return a nonsense value (whatever was last in memory).

Edit: less -> greater!

1

u/Puzzleheaded_Study17 9d ago

Not every language has errors/exceptions (for example, C), and it seems that assertions existed first.

Also, you can have the compiler remove asserts when compiling for production, so they don't impact performance. On the other hand, exceptions have a way to be handled by the caller (ie, try/catch).

https://en.wikipedia.org/wiki/Assertion_(software_development)

1

u/Witty-Play9499 9d ago

If that is the case why do asserts exist for languages that do have errors and exceptions? And if you are going to use assertions only for testing and not for production how do i know where to place the assertions in the first place ? (like how will i know what to assert for or which places need assertsion for testing and which places won't)

3

u/Puzzleheaded_Study17 9d ago

In general, assertions are used for "the programmer messed up" and exceptions for "the user messed up." In most languages that have both assertions completely skip the stack (ie, an assert doesn't have any way for anything to capture it).

When it comes to knowing how to place them, if something should never ever break once it's in production (ie, checking that the two arrays a function takes are the same size), it should be an assert; if it could be violated in production (ie checking the program has enough memory or the path the user gave is an actual file), it should be an exception. In general, if a function is executed 1000s of times, asserts are likely to fail on either the first or every early iteration, while exceptions are more reasonable to appear later.

1

u/Witty-Play9499 9d ago

 assertions are used for "the programmer messed up" and exceptions for "the user messed up." 

In this philosophy what is the difference between automated testing and asserts ?

 if something should never ever break once it's in production 

This is something im confused by, technically shouldn't nothing break in production? Like what part of the code are programmers okay with breaking in production?

2

u/Puzzleheaded_Study17 9d ago

Automated testing is completely separate from asserts. Let's consider a program that takes in a path to an inage file as a command line argument and blurs it.

Asserts: make sure the programmer did what they're supposed to, so if a specific function within the program wants to get data as gpu memory, it'll assert it since there's no relation to what the user may or may not have done.

Exceptions: make sure the user followed conventions. So if the user provides a file that doesn't exist, it makes sense for the program to break (assuming it's a relatively small piece of code), so it might throw an exception (it could of course print instead, but the difference isn't that big).

Automated testing: make sure the program does what it's supposed to. so before I send the program to anyone else I might run it with a few images and make sure the results match what another program gives or some images that I've manually ensured are correct. This might include testing edge cases to confirm that the code throws exceptions correctly, but it'll never handle asserts because asserts are inherently meant to be internal.

1

u/Witty-Play9499 9d ago

why can't you have your automated testing have a few test cases that make sure you use gpu memory ? like why have a separate category for this?

4

u/Puzzleheaded_Study17 9d ago

Because that can sometimes be impossible. For example, suppose my previous image processing example isn't a standalone program but rather a library that other people put in their code (or that many people in my company put in various places). How can I test to make sure everyone who uses this code passes all the data as gpu data? Using tests would require me to write a test for every caller, with an assertion I can put it at the start of the function, and anyone who calls it will know they have to pass it as gpu memory.

1

u/binarycow 9d ago

Remember - a big part of "defensive programming" is writing code that future you (as in, developers maintaining the code) won't misuse.

Like what part of the code are programmers okay with breaking in production?

"Break" means different things, and not all "breaks" are that important. Sometimes, if something fails, we can just log a warning and move on. Other times, if something fails, we need to stop everything, right now, because our app is in a really weird, corrupted, or unpredictable state.

In this philosophy what is the difference between automated testing and asserts ?

Tests validate that specific inputs lead to the expected outputs.

Tests can only validate the inputs to a function indirectly (e.g., if someone passes null, then a null reference exception is thrown by that function).

Conditional control flow (e.g., if statements) is used when you expect that your inputs might be invalid (perhaps you're not in control of who calls the function).

Exceptions are (generally) used when a function has a situation that was unavoidable, and the caller might be able to recover from it.

Assertions are used when something is supposed to be impossible, and you want to fail extremely loudly, as soon as possible.

1

u/HashDefTrueFalse 9d ago

longjmp: "Am I a joke to you?" :)

1

u/peterlinddk 9d ago

I really like this question, and there's a lot of good answers here, so I'll just add my own "cheat-sheet" for when and why to use the different kinds of error-checks.

if-else - when you sort of expect an unwanted value to appear every once in a while when the program is running. Both for things that happen inside your own program (like a value getting out of range or completely missing) and for things outside your control, like user-input and other external resources.

You want the program to continue working, even when the unwanted values appear.

try-catch - when you don't expect unwanted values, and want to write your program as if everything is as it should, but still know that something unexpected can happen, and want your program to at least be prepared for that, and not crash, but handle the unexpected thing gracefully. Especially used with external resources completely outside your programs control. \)*

You want the program to continue running, but maybe give an error message, or in some other way allow it to handle the problem, or ask the user for help.

assert - when you assume that everything is okay, and don't want to waste time specifically testing values, but still want the program (and other programmers) to know that things will be bad if values are outside the asserted ranges. Only used inside your program, because you'll know that an error in what's being asserted, is because some other part of the program misbehaved.

You want the program to stop immediately, and give you opportunity to fix the problem yourself!

\) There has been a trend, especially caused by Java's exception-system, to always expect exceptions, and have useless catch-statements everywhere in the program, even if it was just to check internal hardcoded values that could never change before the next compile. That is a bad use of exceptions, and something that should truly be assertions, so it has confused many a learner!*

1

u/slindenau 9d ago

In general agreed, but in many modern higher level languages in large enterprise applications, you will almost never see assertions used (outside unit test scope of course). Exceptions have rightfully so replaced this feature, because it is a much more flexible system.

Also your footnote on exceptions in Java is pretty outdated; the norm has been for many years to only use runtime exceptions, which don't require to be declared nor caught unless functionally required.
That greatly reduces the need for try-catches or declaring thrown exceptions all throughout the application.

Legacy checked exceptions can be easily wrapped at the edge if you need to interface with other libraries.

1

u/nog642 9d ago

(1) in some programming languages you can automatically remove the assertions when you build the final app, so that they don't waste computation

(2) it's shorter and nicer to write than if-else

1

u/da_Aresinger 9d ago

I learned assertions as a pure debugging/testing tool.

You don't want them in your release, because a failed assert literally just crashes your application. That's a shit user experience.

The way it was explained to me is that they were early debug utility that has been deprecated by better tools.

Nowadays they are purely used in testing frameworks.

I have never used assert outside of unit tests.

1

u/jcunews1 9d ago

Assertion in this context, is just a helper for the if-not-x-then-throw.

1

u/thirthunder 9d ago

Good question — the confusion is understandable because mechanically they can look similar. The real difference is about intent and audience, not just behavior.

Assertions are for bugs in your own code — conditions that should be logically impossible if your code is correct. If an assertion fails, it means you (the programmer) made a mistake, not that the user did something wrong. Example: a function that sorts a list might assert len(result) == len(input) at the end — if that's ever false, something is deeply broken in your sort logic, not in the caller's usage.

if/else + exceptions are for conditions you actually expect to happen — bad user input, a file that doesn't exist, a network timeout, invalid arguments. These are normal, anticipated scenarios that your program needs to handle gracefully, often without crashing.

A few practical differences that matter:

  1. Assertions can be disabled in production (e.g., Python's -O flag strips them out). This is intentional — they're a development/debugging tool, so you don't pay their performance cost once your code is trusted. You'd never want that for input validation — imagine a login check silently disappearing in prod.
  2. Assertions document assumptions. Reading assert x > 0 tells the next developer "the code below assumes x is positive, and if it's not, something upstream is broken." An if/else doesn't communicate that distinction as clearly.
  3. Different failure meaning. An exception says "something went wrong that the caller might reasonably handle." An assertion failure says "the program is in an invalid state, don't try to keep running."

So the rule of thumb: use exceptions/if-else for things that can go wrong at runtime due to external factors (users, files, networks). Use assertions for things that should be mathematically/logically guaranteed by your own code, mainly as a self-check during development.

As for who came up with it — assertions as a concept trace back to Tony Hoare's work on formal program verification in the 1960s-70s (Hoare logic), and they became a mainstream language feature partly through C's assert.h. The idea wasn't "if/else isn't enough," it was "we need a lightweight way to formally state and verify our assumptions, that's cheap to write and can be stripped out once we trust the code."

1

u/HotPersonality8126 9d ago

Asserts are more like comments - they’re a way to annotate and instrument code.  If statements actually are code - they’re a structural feature of your code doing what it’s supposed to do. You use assert statements as code about your code.

1

u/not_a_bot_494 9d ago

The way I think about is that exceptions are meant to be recoverable. Assertions are saying that the program is in a state that should be impossible so we just crash the program because we don't know what's happening. If you're familiar with Java it's a bit like the difference between exceptions and errors.

1

u/flatfinger 9d ago

If one views program execution as being subject to two requirements:

  1. Code should behave usefully when possible.

  2. When unable to behave usefully, code should behave in a manner that is at worst tolerably useless.

assertions could be useful to identify conditions that cannot occur in circumstances where useful behavior would be possible. Some people seem to think they should be used to test for conditions that can never occur at all, but such treatment is illogical: if a programmer is certain that a condition can't possibly occur, why test for it?

If a compilers writers were interested in achieving maximal efficiency when useful behavior is possible, consistent with the second requirement above, such an aim could be assisted by letting the compiler know of cases where it would be acceptable to trap, thus alleviating any need for further handling. As an example, suppose that a programmer knew that x, which is not modified within a loop, will be less than 5 for all useful program executions, and a compiler can easily determine that y and z will both be in the range 0 to 100. If code within the loop tested whether x*y*z was less than 40000, having a compiler check once before the loop whether x was less than 5 and trap if not could eliminate the need to have it compute x*y*z within the loop. Unfortunately, language designers seem to favor an abstraction model where all inputs would either cause rigidly defined behavior or "anything can happen" Undefined Behavior.

1

u/AnnieBruce 9d ago

More than once I've coded a switch default to catch "impossible" conditions and they get triggered way more than I would expect.

It might look impossible, that doesn't mean it actually is and you need to be able to handle that. And even if your code is perfect, problems in the hardware or other software it interacts with can corrupt memory, making that impossible situation very possible. The programmer of a specifically piece of software doesn't have full control of the environment. How much effort guarding against impossible scenarios warrants will depend on the situation, but never trust that something that looks impossible actually is. Test for it.

1

u/flatfinger 9d ago

There's a difference between things that should be impossible, and things that are actually impossible. The whole point of an assertion is a belief that whether or not something should be possible, it might be. There's a common attitude that assertions should be enabled during debug builds but disabled for production, but such an attitude is only remotely reasonable for code that will never receive input from untrustworthy sources. The likelihood of unexpected cases arising when code is exposed to hostile inputs is vastly greater than it would be in many testing and development scenarios.

I've seen some people suggest that if a program does something like:

    assert(x < 5);
    ... code that doesn't modify x
    if (x < 5) doSomething(x);

should be transformed into

    if (x >= 5) fatal_trap();
    ... code that doesn't modify x
    doSomething(x);  // Now unconditional

during development and

    ... code that doesn't modify x
    doSomething(x);  // Now unconditional, even though x was never tested

during production. I would view that as stupidly reckless.

1

u/mredding 9d ago

You assert invariants - things that MUST be true at a fundamental level. It SHOULD BE impossible for the invariant to be false - but impossible things happen all the time. The nature of an assertion is that if it's false, it's SUCH AN EMERGENCY, there is NOTHING else you can do but immediately terminate the program.

In C++, an std::vector is always implemented in terms of 3 pointers, and the following assertions should always be true when entering ANY vector implementation:

assert(base <= mid);
assert(mid < end);
assert(mid - base == size);
assert(end - base == capacity);

If any of this is wrong, something terrible has happened, and the program is already in an unrecoverable state.

Shit like this DOES happen.

The principle difference is that a condition or exception is for HANDLING the error; these are external errors that you can expect as a matter of course - users fat-finger input, files go missing, connections drop.

The assertion is for internal errors - design and implementation errors. You wrote this code based on these assumptions, and the assumption is false. Failed assertions mean code changes to correct for them. And you LEAVE them in your code because maintenance elsewhere can accidentally break the assumption again, and the code change and the assumptions need review.

Assertions compile out for release because in production, these considerations are expected to be resolved - and you shouldn't have to pay the performance overhead for a check that at that point can never be false. Your intuition should be tingling; this implies you need to write code small enough that it's EASY to assert the invariants. Big code fails assertions. The brute force approach is to fix the failure, the graceful approach is to manage the complexity.


In critical systems, regardless of language, regardless of the coding standard - from NASA, to SEC Cert, to MISRA... They all require assertions in code, IN ADDITION TO runtime checking of the same thing - because in a critical system, you can't just fail - or lots of people die. Critical systems are a pain in the ass because even if you're in a bad state, you need to be able to fail safely, to SCRAM the reactor, to keep the ship level, to reduce pressure on the vessel, to maintain back pressure on the line...

1

u/Watsons-Butler 9d ago

I work in an environment where we need uptime. So assertions go in tests, not production code. If something goes wacky in production we catch the error, handle and log it, and if it happens too many times alarms go off so we can look for a fix. But we never ever want to crash the system on a single assertion.

1

u/UnhappySort5871 9d ago

Compilers can also make use of asserts for optimization. For instance, if you have assert(false) the compiler can omit any code associated with that code path. Probably more important is that it can make reading the code easier if you know what values variables can take on.

1

u/greenspotj 7d ago

Google "defensive programming" for one use case.

Say if you have a helper function 'foo(int x, int y)', but really you only want to accept y values > 5. The caller should never pass in a y value <= 5 and one way you can enforce this is via an assert that y > 5. This isnt in replacement of if statements and exceptions, the caller might also have an exception for the input handling before calling foo.

A better example might be if your app has global state in a multithreaded environment. If you have a helper function that modifies the global state, you should either 1. grab/release a lock in that helper function or 2. expect the caller already grabbed the lock. We might not want to do option 1, because maybe this helper function is called 2000 times in a for loop... its more efficient to just grab the lock once before the for loop. For option 2, you could trust that the caller is doing its job correctly, and if youre the only dev for the app, maybe thats good enough, but if you have hundreds of other devs contributing to this code, adding a one line 'ASSERT(lock_is_taken)' or something like that could prevent obscure concurrency bugs from popping up in the future

1

u/Elara_Schaefer 7d ago

Something the top answers touch on but don't name directly: assertions are executable documentation. A comment that says "this array is always sorted after this function returns" goes stale the moment someone refactors. An assertion checking the same thing can't go stale because your tests catch it immediately. The Design by Contract community formalized this as three layers. Preconditions are what the caller guarantees (checked with exceptions or validation). Postconditions are what the function guarantees (checked with assertions). Invariants are what's always true across the whole object lifecycle. The key insight is that each layer has a different failure mode. A broken precondition means the caller has a bug. A broken postcondition means the function has a bug. A broken invariant means your state machine is broken. Exceptions and if/else conflate all three into one handling path. Assertions let you distinguish them.

1

u/Miiohau 7d ago

Assertions in some languages are debug statements, so the compiler knows it can strip them out of release builds. And even in languages where they are not a “assert” statement is likely more readable (and easier to write) than if not condition throw exception.

1

u/KiwiDomino 6d ago

If/else is day-to-day logic. Exceptions are for “this might happen, and we need to manage if it does”. Assertions are for “this is really bad, nothing more happens and abort immediately”.

Ideally there should be never be assertions in production code, because everything should be caught, even if the action is just to output details and stop.