Skip to content
DevMeme
5398 of 7435
JavaScript's Infamous Octal Number Quirk
Languages Post #5917, on Feb 28, 2024 in TG

JavaScript's Infamous Octal Number Quirk

Why is this Languages meme funny?

Level 1: Numbers in Disguise

Imagine you have a special trick where if you put a 0 in front of a number, it becomes a secret code for a different number. You write down “017” on a piece of paper, but because of a hidden rule, it actually means 15. Now your friend just sees “017” and thinks it’s seventeen the normal way. Later, you both compare notes: you say, “My secret code number 017 is 15,” and your friend says, “Well, I read ‘017’ as 17.” You end up arguing whether 15 is the same as 17 – of course it’s not! This is basically what happened in the code. The computer had a sneaky rule (that leading 0 was a clue to use a different counting system), so one “017” wasn’t really the same as the other “017” anymore. It’s like one of the numbers was wearing a disguise!

When the programmers in the picture see this, they’re so shocked that they do a cartoonish spit-take with their coffee. It’s funny because you wouldn’t expect something that looks so simple – comparing two things that look identical – to suddenly turn out different. It’s as if you and your friend pointed at what looked like the same car, but one of you was actually looking at a cleverly painted bicycle. The JavaScript language sometimes has these little surprises, and that’s why the caption calls it a “JavaScript Moment.” It means “oh boy, here’s JavaScript being silly again!” Everyone laughs (and maybe groans a bit) because the rule is kind of absurd, but knowing about it makes you feel like you’ve uncovered a secret prank the language was playing on us.

Level 2: Coercion Confusion

Let’s break down what’s happening in simpler terms. In JavaScript, there are two main ways to compare values for equality: strict equality (===) and loose equality (==). Strict equality is straightforward – it checks if two values are exactly the same without changing anything. Loose equality, on the other hand, will try to be smart and convert one value’s type to match the other if they’re different types. This automatic converting is called type coercion. It’s like JavaScript saying, “I’ll try to make these the same type before I compare them.” That can sometimes cause weird results, as we see in this meme.

Now, about those numbers with a leading zero: if you write a number like 017 in some programming languages (and in older JavaScript), it might be treated as an octal number. Octal means base-8, a way to count using eight digits (0 through 7) instead of the usual ten digits (0 through 9) we use in decimal. So in base-8, “17” doesn’t mean seventeen; it actually means $1 \times 8^1 + 7 \times 8^0$ which equals $8 + 7 = 15$ in decimal. In the code console.log(017 == '017');, the part 017 (without quotes) is a number literal. Because of the leading 0, JavaScript (in non-strict mode) interprets it using that old octal rule. So 017 becomes the number 15 in memory.

On the right side, '017' (with quotes) is just a string of characters. When we use == to compare a number and a string, JavaScript will coerce the string into a number. '017' as a string, when converted to a number, is read as 17 (decimal seventeen). JavaScript, when converting strings to numbers, doesn’t apply the octal rule – it just sees “017” and treats it as the decimal number 17 (leading zeros are generally ignored in this context). So effectively, the comparison is asking: is the number 15 equal to the number 17? Not surprisingly, the answer is false.

In the first comparison console.log(018 == '018');, something slightly different happens. 018 as a literal can’t be octal, because 8 is not a valid digit in base-8. So JavaScript falls back to interpreting it normally as the decimal number 18 (no funny business there). The string '018' then gets coerced into the number 18. So it ends up comparing 18 to 18, which is true – they match. In summary:

  • 017 (literal) → interpreted as octal 17 → equals decimal 15.
  • '017' (string) → coerced to a number → equals decimal 17.
  • 15 (left side) is not equal to 17 (right side) → false.

But for the other one:

  • 018 (literal) → not a valid octal, so taken as decimal 18.
  • '018' (string) → coerced to number → decimal 18.
  • 18 equals 18 → true.

The meme pairs this code output with a Team Fortress 2 reaction image. Team Fortress 2 is a video game, and it has some iconic characters that people use in memes. In the bottom panel, two of these characters are shown recoiling and spilling their coffee in shock. The caption says “JavaScript Moment,” which is basically saying “this kind of wacky result is something that only happens in JavaScript.” It’s a lighthearted way to show how programmers feel when they encounter a surprise like this. They’re dramatizing the reaction – most developers won’t literally fall out of their chairs throwing coffee, but it feels shocking enough to warrant that kind of spit-take reaction.

For a newer developer, the lessons here are: JavaScript can be quirky, especially with older features. It’s important to know that writing numbers with a leading zero is a bad idea in JavaScript because of this octal business. In modern code, you’d rarely do that on purpose (and strict mode will stop you), but it might pop up in legacy code or weird scenarios. Also, relying on == (loose equality) can lead to confusing outcomes due to type coercion. A common best practice is to use === (strict equality) so JavaScript doesn’t do any secret conversions behind your back. If we had done 017 === '017', JavaScript would not try to convert types – it would see one side is a number (15) and the other is a string, say “these are different types, I’m not going to pretend they’re the same,” and immediately return false. Similarly, 018 === '018' would also be false, because 18 (number) is not the same type as "18" (string). In short, the code in the meme is a demo of a weird corner of the language that’s funny to see but also a teaching moment: be careful with loose equality and with how you write your numbers.

Level 3: Base-8 Betrayal

For experienced JavaScript developers, this meme hits on a classic LanguageQuirk that’s equal parts hilarious and painful. The code snippet shows two console.log comparisons:

console.log(018 == '018'); // true 
console.log(017 == '017'); // false

At first glance, both lines look similar — comparing a number to an identical-looking string. But the outcome is unexpectedly different, and that’s the punchline. Why does the first comparison return true while the second returns false? It’s the result of an octal gotcha teamed up with JavaScript’s loose equality shenanigans. In JavaScript’s older syntax, writing 017 as a numeric literal doesn’t mean seventeen; it’s actually interpreted as an octal (base-8) number. Octal 17 in base-8 equals 1·8^1 + 7·8^0 = 15 in our usual decimal system. Meanwhile, 018 isn’t a valid octal (because of the digit 8), so JavaScript interprets that one as a normal decimal 18. This difference is invisible in the code – there’s no obvious sign that 017 is being taken as something other than “seventeen” – which is why it catches developers off guard. It feels like the language betrayed our expectations: hence the Base-8 betrayal.

On top of that, the use of == (the loose equality operator) brings in type coercion. The rule is: if you compare a number to a string with ==, JavaScript will try to convert the string into a number. So in the second case, we have the number 15 (from the octal literal 017) being compared to the string '017'. The string '017' converts to the number 17 (JavaScript reads it as decimal seventeen when converting a string to number). Now we’re essentially checking if 15 equals 17 – clearly false. In the first case, we got the number 18 (from 018) vs the string '018', which gets converted to 18. So it checks 18 == 18 – that’s true. This is one of those “javascript_wtf” moments that’s both frustrating and funny. Frustrating, because if you didn’t know about it, you’d be scratching your head why one comparison fails. Funny, because once you do know, it exemplifies the weird, wild west nature of JavaScript’s early design decisions.

The meme’s bottom panel (the Team Fortress reaction meme) nails the emotional response. Two characters flinging back with spilled coffee is an exaggeration of a developer’s reaction: “What on earth did I just witness!?”. Seasoned devs have a bit of an inside joke here – JavaScript strikes again! It’s the kind of thing that might make a coder do a double-take, maybe spit out their coffee in surprise (just like in the image). The caption “JavaScript Moment” says it all: these scenarios are emblematic of the language’s reputation for quirky behavior. We’ve all been there: perhaps you were debugging why "08" wasn’t working in an older browser, or why parseInt('09') returned 9 while parseInt('08') gave 0 in some environments. This 017 vs '017' comparison is cut from the same cloth.

There’s a shared understanding among developers that JavaScript can be both powerful and peculiar. The community often responds with humor to these oddities – it’s a coping mechanism for the head-scratchers we encounter. This meme is poking fun at how something as simple as writing a number with a leading zero can trip up code logic. It highlights a form of technical debt in the language: a design choice from the past that lingers on and causes “gotchas.” And because changing it would break old code, it persists, biting unsuspecting devs now and then. The fact that == tries so helpfully to equate different types is another design decision that has backfired countless times (leading to advice like “always use ===”).

In real-world scenarios, this exact issue could surface if someone, say, stored numeric IDs or values with leading zeros as raw numbers. A check that should succeed might mysteriously fail, simply because the number was interpreted in octal. Many a time, a senior engineer has had to gently explain to a junior (or to their past self) why 008 might throw an error or why a certain comparison is acting wonky. It’s the kind of bug that’s obvious only after you’ve realized what’s happening. That’s why this meme resonates: it’s a wink to all those who have chased down a bizarre bug, only to discover a subtle language quirk at play. It never gets old, indeed, because each new generation of JavaScript devs eventually stumbles into one of these traps and has their own “spilled coffee” moment.

Level 4: Leading Zero Legacy

Deep inside JavaScript’s design is a relic from older programming languages: leading-zero octal literals. This meme highlights how a number like 017 is parsed not as seventeen, but as an octal (base-8) value. Octal was common in early computing (back when bytes were grouped in 3-bit chunks) and languages like C adopted a convention: any integer literal starting with 0 was interpreted in base-8. JavaScript’s first implementation inherited this quirk. Under the hood, the JavaScript parser still recognizes 017 as an octal literal (yielding decimal 15) in non-strict mode. However, 018 contains an invalid octal digit (8 is not allowed in base-8), so the parser gracefully falls back to interpreting 018 as a normal decimal 18. In other words, the presence of the digit 8 in 018 effectively “breaks” the octal rule and yields a decimal number, whereas 017 stays in octal-land.

This behavior persisted until modern times for backward compatibility. ECMAScript 5 (2009) introduced strict mode, which outright forbids old-style octal literals, treating them as syntax errors to prevent this confusion. Today, the recommended syntax for octals is explicit (e.g. 0o17 for octal 17). But in legacy or non-strict code, the grammar’s “leading-zero means octal” rule still lingers. What’s more, JavaScript’s type coercion rules then come into play: the loose equality comparison == triggers the language’s Abstract Equality Comparison algorithm. When comparing a number to a string, JavaScript will attempt to convert the string to a number. So '017' (a string) is coerced via ToNumber into 17 (decimal) — modern JavaScript’s string-to-number conversion treats it as base-10 by default, ignoring any legacy octal implication in the string form. Meanwhile, on the left side, the literal 017 had already been parsed as numeric 15. Thus the comparison essentially boils down to 15 == 17, which of course is false. By contrast, 018 == '018' becomes 18 == 18 under the hood, yielding true.

It’s a perfect storm of historical language design and spec rules: a numeric literal parsing quirk colliding with the loose equality operator. The ECMAScript specification’s fine print on numeric literals and type conversion algorithms is the culprit here. Seasoned developers might recall that these edge cases are thoroughly documented in JavaScript’s lore (the famous "JavaScript WTF" collections and even academic discussions on language design pitfalls). In summary, the meme’s code snippet is not a random glitch but a predictable outcome given the language’s grammar and type conversion rules. It’s a fascinating (if arcane) example of how legacy features in a language’s spec can yield results that feel counterintuitive today.

Description

This is a two-panel meme that perfectly captures a frustrating and non-intuitive aspect of JavaScript. The top panel shows a code editor with a JavaScript file named 'index.js'. The code consists of two lines: `console.log(018 == '018');` and `console.log(017 == '017');`. The terminal output below shows 'true' for the first line and 'false' for the second. The bottom panel features a still from the game Team Fortress 2, with two characters laughing hysterically, captioned with 'JavaScript Moment'. The technical humor lies in JavaScript's legacy handling of octal (base-8) literals. A number starting with a zero (like 017) is treated as octal. Since octal only uses digits 0-7, `017` is valid octal for the decimal number 15. However, `018` is an invalid octal number, so JavaScript silently treats it as the decimal number 18. When using the loose equality `==` operator, `18 == '018'` coerces to `18 == 18` (true), while `15 == '017'` coerces to `15 == 17` (false). This counter-intuitive behavior is a classic 'gotcha' that leads to bugs and developer frustration

Comments

63
Anonymous ★ Top Pick JavaScript's loose equality is the reason code reviews exist. It's the only place where `017` being unequal to `'017'` is somehow logical
  1. Anonymous ★ Top Pick

    JavaScript's loose equality is the reason code reviews exist. It's the only place where `017` being unequal to `'017'` is somehow logical

  2. Anonymous

    JavaScript Annex B: 017 time-travels to octal 15, 018 gets deported back to decimal, and your diff suddenly looks like a PDP-11 permissions audit

  3. Anonymous

    After 20 years in the industry, you'd think we'd all remember to use strict mode by now - but no, we're still discovering that JavaScript treats 018 as decimal because even octal numbers have standards, while 017 happily becomes 15 in base-10. It's like finding out your production config parser has been silently misinterpreting UNIX file permissions for years

  4. Anonymous

    Ah yes, the classic '018 == "018"' returns true but '017 == "017"' returns false - because JavaScript sees that leading zero on 017 and thinks "ah, octal!" converting it to decimal 15, while 018 is an invalid octal (no 8 in base-8) so it just shrugs and treats it as decimal 18. This is why we have 'use strict' and ESLint rules, folks. Nothing says 'battle-tested language design' quite like having to explicitly disable footguns from 1995

  5. Anonymous

    In JS, leading zeros are a time machine: 017 is octal 15, 018 is decimal, and '==' smiles through it - use strict modules or stop zero-padding like it’s 1995

  6. Anonymous

    Loose equality is fine - until a single leading zero from a CSV turns billing into base‑8 and on‑call into exponential backoff

  7. Anonymous

    JS octals: valid only sans '8', because who needs consistent parsing when you have history to honor?

  8. @flyingshine 2y

    still better than python

    1. @Iggdraisil 2y

      Have you ever seen Python moments like that?

      1. @flyingshine 2y

        ahahaa so funny, silly python developers😂

      2. @karumsenjoyer 2y

        how often you write shitty code like that?

      3. @Terroo 2y

        Bruh That's literally gh repo about python moments https://github.com/satwikkansal/wtfpython

      4. @ZgGPuo8dZef58K6hxxGVj3Z2 2y

        Try using both spaces and tabs

    2. @Johnny_bit 2y

      That's one way to start a flamewar...

  9. @HeTema 2y

    Why first one is true and second one is false?

    1. @Agent1378 2y

      Probably, because of leading zero second is treated like it is base 8

  10. @vladislav805 2y

    017 (oct) = 15 (dec) 018 = 18

    1. @purplesyringa 2y

      I think the weird part is that "017" is implicitly converted to 17 instead of 15

      1. @azizhakberdiev 2y

        ig these conversions aren't considered thoroughly. For binaries and hexadecimals it works well. parseInt however recognizes only hexadecimals

    2. @FlyLikeSoarin 2y

      Why would base 8 literal with 8 in it be treated as anything other than error... This language is cancer

      1. @deadgnom32 2y

        2.3.1 An integer constant is a sequence of digits. An integer is taken to be octal if it begins with 0, decimal otherwise. The digits 8 and 9 have octal value 10 and 11 respectively.

      2. @deadgnom32 2y

        in old C it wasn't an error either

        1. @FlyLikeSoarin 2y

          Day ruined

          1. @Agent1378 2y

            Surprised?

            1. @FlyLikeSoarin 2y

              Yeah, a little bit. I would've been less surprised if 018 somehow did something unexpected in modern c++ with all of its bloat. The more you know

              1. @Agent1378 2y

                C is the same junks as JS, it was called "assembly for lazy people". Maybe it is better now but doubt it - CVEs still come in every day

                1. @FlyLikeSoarin 2y

                  I think that in terms of security any maintained language that gets its vulnerabilities fixed in timely manner is as good as any other. For me C is not the same junks as JS, but it is an opinion. In the end of the day it all comes down to personal preference and the problem that needs to be solved.

                  1. @Agent1378 2y

                    A lot of vulnerabilities should not have appeared in the first place. A little bounds check by compiler, a little type safety would do. But no, people still think it is all "makes code slow" (in places where this doesn't matter) and compile with no checks and etc.

    3. @Supuhstar 2y

      018 isn't valid octal tho 😭😭😭 it should reject it with an error

  11. @heygambo 2y

    018 is not a valid octal number and javscript will treat it as a decimal number as a fallback. better would be to maybe throw an error I suppose

  12. @heygambo 2y

    https://www.youtube.com/watch?v=FhNwLvCYlY4

  13. @casKd_dev 2y

    octal vs decimal?

  14. @IHateCupsAndDonuts 2y

    455 257 weekly installations <3

  15. @avrdudec 2y

    let’s pretend static comparison doesn’t exist

    1. Deleted Account 2y

      омагааах

    2. @ovarn 2y

      Something was changed in the V8 a couple years ago, if I am not mistaken. Where did you execute it?

      1. @purplesyringa 2y

        It's not V8, it's == vs ===

        1. @ovarn 2y

          Oh, I see

  16. @SamsonovAnton 2y

    It still baffles me that so many programming languages use line breaks [as end of statement] to tokenize the code. 🤓

  17. @SamsonovAnton 2y

    Your cryptic message looks like some shell script bomb.

  18. @Kyngo 2y

    this is actually intended to be like that, i don't see the problem with this

  19. @Sp1cyP3pp3r 2y

    Programming languages for pussies, use runes

    1. @AmindaEU 2y

      I would like to hear more 👀

  20. @deadgnom32 2y

    why?

  21. @misesOnWheels 2y

    Classic "I have no fucking clue how my programming language works so I'll make fun of it" moment

  22. @deadgnom32 2y

    because of more syntactic noise? nonsense. you just got used to it. let me bring an example

  23. @deadgnom32 2y

    what is simpler to read?

  24. @deadgnom32 2y

    foo x = let s = sin x c = cos x in 2 * s * c

  25. @deadgnom32 2y

    or

  26. @deadgnom32 2y

    foo x = let { s = sin x; c = cos x; } in 2 * s * c

  27. @deadgnom32 2y

    if your code is small enough, then all those curly and round braces are just noise. imagine it to be folded like multiple times, then instead of a couple of lines of information we will have a lot of curly braces that get opened and closed. another example what is easier to read? f1(f2(f3(f4(x)))) or f1 . f2 . f3 . f4 x

    1. @deadgnom32 2y

      it's about, the thing, that with more experience you need less syntax for short concise solutions

  28. @deadgnom32 2y

    why?

  29. @deadgnom32 2y

    it has more syntax you don't need to understand either way. and formatting is more machine readable rather than human readable.

  30. @deadgnom32 2y

    it's about using right tools for right tasks. i know there are huge monolith python projects out there and its a pain to work with them. but if you just need a small script like <1000lines. it's even easier to comprehend if it has less syntactic noise.

  31. @deadgnom32 2y

    for composition too

  32. @deadgnom32 2y

    yeah. people just take Java and write 10000 lines of SOLID code, while the same fits in a mere 200 lines of SQL script

  33. @deadgnom32 2y

    for example, how to reduce the amount of lines of code by more than 10 times? throw away ORM. even in python SQL code like: CREATE TABLE id_table(id INTEGER PRIMARY KEY); a minimal table. requires this much of code from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass class IdTable(Base): __tablename__ = "id_table" id = Column(Integer, primary_key=True)

    1. @deadgnom32 2y

      and it even doesn't provide a proper type hints. I'm really unable to comprehend the advantage. it doesn't provide even type hinting it requires different code for different data bases... so it has all the disadvantages of the pure sql solution, but the more complex it gets, the harder it is to use the ORM compared to pure SQL. my last experience with it was to drop the development of the data model after 250 lines and 3 days of complicated OOP code. and then solve the task in 16 lines and a couple of minutes of declarative code.

  34. @FunnyGuyU 2y

    JS was initially designed to execute any crap, any ravings of any madman at any cost. All subsequent evolution of this monstrosity is no more than futile attempts to formalize it ang ram some logical explanations of its behavior down our throats.

  35. @AlexAparnev 2y

    still better than excel

  36. Ricardo Gross 2y

    Wow, this post definitely caught my eye. Cant wait to see more from you. 😉

  37. @colllapse 2y

    not really js moment. C did this too back in a day.

Use J and K for navigation