<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/iterating-backwards-in-c-yexncvhav" -->

---
title: Iterating Backwards in C | daily.dev
description: Writing a backwards loop with an unsigned size_t index as 'i >= 0' is always true and wraps to SIZE_MAX when count is 0. Switching to a signed ssize_t fixes...
canonical: https://daily.dev/posts/iterating-backwards-in-c-yexncvhav
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Iterating Backwards in C | daily.dev
og:description: Writing a backwards loop with an unsigned size_t index as 'i >= 0' is always true and wraps to SIZE_MAX when count is 0. Switching to a signed ssize_t fixes...
og:url: https://daily.dev/posts/iterating-backwards-in-c-yexncvhav
og:image: https://api.daily.dev/og/posts/YExNcvHAV.png
og:image:alt: Iterating Backwards in C
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Iterating Backwards in C

**[Hacker News](https://daily.dev/sources/hn)** · 3 min read · 0 upvotes · 0 comments

## Summary

Writing a backwards loop with an unsigned size_t index as 'i >= 0' is always true and wraps to SIZE_MAX when count is 0. Switching to a signed ssize_t fixes correctness but adds portability issues, since ssize_t is POSIX-only and needs a manual definition on Windows, plus signed/unsigned conversion and truncation problems on 32-bit types. The correct pattern is 'for(size_t i = count; i--; )', which uses postdecrement so i is always in bounds inside the loop body and the condition naturally stops at zero. Unsigned wraparound is well defined in C, so the final decrement past zero is safe.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.davidpriver.com/Iterating-backwards-in-C.html>

## Questions this post answers

### How do I correctly iterate backwards over an array using size_t in C without underflow bugs?

Use the postdecrement pattern: for(size_t i = count; i--; ). Inside the loop body i is always a valid in-bounds index because it's decremented after being used in the condition, and the loop ends correctly when i would go below zero since unsigned wraparound in C is well defined. This avoids the common bug of writing 'for(size_t i = count - 1; i >= 0; i--)', where i >= 0 is always true for an unsigned type and count of 0 causes i to wrap to SIZE_MAX.

_Developers hardening C loop logic can compare idioms like this one on daily.dev._

### Why does my C loop condition i >= 0 never become false when i is size_t?

Because size_t is unsigned, so i >= 0 is always true and the compiler may warn about a tautological comparison. If count is 0, computing count - 1 for the initial value also wraps i around to SIZE_MAX instead of producing a negative number, making the loop behave incorrectly or run far too long.

_Anyone debugging unsigned loop quirks in C can keep gotchas like this handy via daily.dev._

### Should I use ssize_t instead of size_t for a backwards loop index in C?

It works but introduces trade-offs: ssize_t is a POSIX-specific type that must be manually defined on Windows, and using a signed index can trigger C's implicit signed/unsigned conversion rules, with possible truncation or promotion problems on 32-bit types. A cleaner alternative is keeping the index as size_t and using the postdecrement form for(size_t i = count; i--; ) instead of switching to a signed type.

_Teams weighing portability trade-offs in C loop design can track patterns like this on daily.dev._

## Community take

How the wider developer community reacted, aggregated from 1 discussion and 56 comments across hackernews (as of 2026-10-30).

**TL;DR:** Commenters largely agree the postdecrement 'i-->0' / 'for(size_t i=count; i--;)' idiom is correct and well-known, though they're split on whether it's too clever to be readable, with many offering alternative patterns (signed loops, pointer math, C++ reverse iterators) and debating whether unsigned-vs-signed indices or backwards-counting performance claims actually hold up.

**Sentiment:** 35% positive · 50% mixed · 15% skeptical

**The case for**

- The postdecrement idiom works correctly for both signed and unsigned index types and is a long-known, if obscure, trick.
- Some argue it can be marginally faster since comparing to zero can let the decrement's flag be reused without a separate compare instruction.
- Signed loop counters avoid surprising conversion/overflow pitfalls compared to unsigned ones, according to several commenters.

**The pushback**

- Many find the 'i-->0' and 'for(size_t i=count; i--;)' forms needlessly cryptic or obfuscated on first read.
- Several dispute the claim that counting down is faster, saying modern compilers/CPUs make the difference negligible or unpredictable.
- Using the decremented loop variable elsewhere risks subtle bugs like infinite loops if it's modified unexpectedly.
- Recommending plain 'int' for indices is criticized as a poor choice since int is often only 32-bit on 64-bit platforms.

**By community**

- hackernews (mixed): Discussion mixes appreciation for the clever postdecrement idiom with real skepticism about its readability, its performance justification, and the best practice for signed vs unsigned indices.

**Hottest debate:** Whether unsigned (size_t) or signed index types are the better default for loop counters and array indices.

**Open questions**

- Does counting down actually produce measurably faster code on modern CPUs, or is any difference noise?
- Is there a readability-safe way to get the benefits of the postdecrement idiom without confusing readers unfamiliar with it?

**Highlights**

> Hmm.  I think both of you are both right and wrong! That is, I claim that while it is no faster to execute a comparison against zero, it still can be faster to finish a loop at zero.  This is because internal to the processor, the comparison and the branch are two separate ops.  The comparison essentially does an arithmetic operation that sets a flag but throws away the result, and then the branch acts on it. But if you finish at zero, the flag you are using is already set by the decrement, so no additional comparison op is necessary.  Now normally this doesn't matter, since the processor is multiscalar with sufficient execution ports to fit in an extra comparison with no increase in total cycles spent.  But in a tiny and highly optimized loop, occasionally you can get about 20% gain from having one fewer op to execute. Getting this to actually happen can be complicated because modern processors do op-fusion that can "fuse" the CMP and branch back into something that can be executed on a single port.  But DEC-JNZ can be fused too, which still gives it an advantage.  More details on this here: https://chipswiki.org/Macro-operation_fusion Now does anyone want to finish the "ground speed readout" routine (https://theaviationgeekclub.com/sr-71-blackbird-pilot-tells-...) by pointing out actually how with modern processors I'm actually wrong too?
> — [nkurz on hackernews](https://news.ycombinator.com/item?id=50300020)

> I was unclear in the article, but in my defense I wrote it in a hurry on the couch after seeing the convoluted backwards loop an llm had extruded. I meant if you are actually doing something with the index variable other than indexing. Mixing signed and unsigned types, possibly of different sizes, has surprising behavior to many programmers who don’t have the “usual arithmetic conversions” memorized. If you’re working in size_t the result simplifies: everything becomes size_t. And you generally have size_t anyway as you’re using the result of sizeof or an allocation size passed to your allocation function. In general I want to avoid signed types for counts and sizes as then you have to consider both boundary conditions which is easy to mess up.
> — [david2ndaccount on hackernews · 1 comments](https://news.ycombinator.com/item?id=50300590)

> > for(size_t i = count; i--; ) { /* find needle */ } As someone who only writes C or C++ occasionally, I'd consider this a good submission for an obfuscated C contest. It really does look simple at first glance. Then you start to wonder what the stop condition is. Then you realize the i-- is the stop condition. Then you wonder how the whole thing works at all. (The description explained the trick well, and the construct makes sense once you understand it, but I still think this is one example of something that is "too simple" and becomes cryptic) After all the stuff you hear about undefined behavior, I'm also honestly surprised that wraparound on unsigned types is not undefined behavior.
> — [xg15 on hackernews · 1 comments](https://news.ycombinator.com/item?id=50300733)

> His final solution is so tricky.  I needed to re-read it a few times to understand the trick of using post decrement in the for loop condition.  Personally, I would never write code like that.  I would always find a way to use ssize_t. This part was an interesting requirement:     > ssize_t is a POSIX thing so you'd have to define it yourself on windows. That seems like a lot of mental gymnastics to avoid this very simple macro:     > #if defined(_WIN32) && !defined(ssize_t)     > #define ssize_t SSIZE_T     > #endif Read about the Win32 macro SSIZE_T here: https://learn.microsoft.com/en-us/windows/win32/winprog/wind... To me, this solution is more readable and maintainable:     > for (ssize_t i = count - 1; i >= 0; i--)     > {     >     if (haystack[i] == needle)     >     {     >         *where = i;     >         return 1;     >     }     > }     > return 0; Alternatively, you can use a while loop where the condition appears at the end of the loop:     > size_t i = count - 1;     > while (1)  // true     > {     >     if (haystack[i] == needle)     >     {     >         *where = i;     >         return 1;     >     }     >     if (0 == i)     >     {     >         return 0;     >     }     >     --i;     > }
> — [throwaway2037 on hackernews](https://news.ycombinator.com/item?id=50300347)

> That's the price of innovation, I guess; before C invented its "for"-loop, other languages had their equivalents generally looking like this:     FOR var = start [DOWN] TO end BY step DO ... END No custom increment expressions, and "step" even usually was only allowed to be a constant (in which case, the language usually didn't have DOWN specifier and used the step's sign instead). Then Dennis Ritchie invented C, and ditched the traditional "do"-loop (Fortran started that construct with DO, so it was called DO loop; Algol used FOR instead, but most people preferred the Fortran's nomenclature... until C came to dominate, but that wouldn't happen for yet another 20 years) in favour of his invention because he felt that easily iterating through linked lists or strings was a much more pressing concern:     for(p=head; p; p=p->next) { ... }     for(s=str; *s; s++) { ... } The downside, of course, is that now you can't straightforwardly write    for size_t i = count - 1 downto 0 do       ...    end
> — [Joker\_vD on hackernews](https://news.ycombinator.com/item?id=50299494)

**Source threads**

- [hackernews](https://news.ycombinator.com/item?id=50249598) · 91 points · 56 comments

## Similar posts on daily.dev

- [Almost Always Unsigned \#](https://daily.dev/posts/almost-always-unsigned--fva0lynn8) · Hacker News · 1 upvotes · 0 comments
- [C3](https://daily.dev/posts/c3-r4ml1zhe4) · Lobsters · 0 upvotes · 0 comments
- [Structured iteration](https://daily.dev/posts/structured-iteration-hri4jxsmi) · C\+\+ · 3 upvotes · 1 comments
- [C\+=0.25](https://daily.dev/posts/c-0-25-fbbk4rxps) · The Daily WTF · 5 upvotes · 0 comments

---

Tags: [#c](https://daily.dev/tags/c)

[View this post on daily.dev](https://daily.dev/posts/iterating-backwards-in-c-yexncvhav)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Iterating Backwards in C","url":"https://daily.dev/posts/iterating-backwards-in-c-yexncvhav","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/iterating-backwards-in-c-yexncvhav"},"datePublished":"2026-10-30T03:10:32.303Z","dateModified":"2026-10-30T07:41:34.078Z","description":"Writing a backwards loop with an unsigned size_t index as 'i \u003e= 0' is always true and wraps to SIZE_MAX when count is 0. Switching to a signed ssize_t fixes...","image":"https://media.daily.dev/image/upload/s--qPvKM23u--/f_auto/v1722860399/public/Placeholder%2009","thumbnailUrl":"https://media.daily.dev/image/upload/s--qPvKM23u--/f_auto/v1722860399/public/Placeholder%2009","isAccessibleForFree":true,"articleSection":"Hacker News","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Hacker News","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/hn","url":"https://daily.dev/sources/hn"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/iterating-backwards-in-c-yexncvhav","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"c","timeRequired":"PT3M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Hacker News","item":"https://daily.dev/sources/hn"},{"@type":"ListItem","position":3,"name":"Iterating Backwards in C"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/iterating-backwards-in-c-yexncvhav#faq","mainEntity":[{"@type":"Question","name":"How do I correctly iterate backwards over an array using size_t in C without underflow bugs?","acceptedAnswer":{"@type":"Answer","text":"Use the postdecrement pattern: for(size_t i = count; i--; ). Inside the loop body i is always a valid in-bounds index because it's decremented after being used in the condition, and the loop ends correctly when i would go below zero since unsigned wraparound in C is well defined. This avoids the common bug of writing 'for(size_t i = count - 1; i \u003e= 0; i--)', where i \u003e= 0 is always true for an unsigned type and count of 0 causes i to wrap to SIZE_MAX. Developers hardening C loop logic can compare idioms like this one on daily.dev."}},{"@type":"Question","name":"Why does my C loop condition i \u003e= 0 never become false when i is size_t?","acceptedAnswer":{"@type":"Answer","text":"Because size_t is unsigned, so i \u003e= 0 is always true and the compiler may warn about a tautological comparison. If count is 0, computing count - 1 for the initial value also wraps i around to SIZE_MAX instead of producing a negative number, making the loop behave incorrectly or run far too long. Anyone debugging unsigned loop quirks in C can keep gotchas like this handy via daily.dev."}},{"@type":"Question","name":"Should I use ssize_t instead of size_t for a backwards loop index in C?","acceptedAnswer":{"@type":"Answer","text":"It works but introduces trade-offs: ssize_t is a POSIX-specific type that must be manually defined on Windows, and using a signed index can trigger C's implicit signed/unsigned conversion rules, with possible truncation or promotion problems on 32-bit types. A cleaner alternative is keeping the index as size_t and using the postdecrement form for(size_t i = count; i--; ) instead of switching to a signed type. Teams weighing portability trade-offs in C loop design can track patterns like this on daily.dev."}}]}
```

