Skip to content
DevMeme

JavaScript's Infamous Octal Number Quirk — Meme Explained

JavaScript's Infamous Octal Number Quirk
View this meme on DevMeme →

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.

Comments (63)

  1. Anonymous

    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

    still better than python

  9. @HeTema

    Why first one is true and second one is false?

  10. @vladislav805

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

  11. @heygambo

    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

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

  13. @casKd_dev

    octal vs decimal?

  14. @IHateCupsAndDonuts

    455 257 weekly installations <3

  15. @avrdudec

    let’s pretend static comparison doesn’t exist

  16. @SamsonovAnton

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

  17. @SamsonovAnton

    Your cryptic message looks like some shell script bomb.

  18. @Kyngo

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

  19. @Sp1cyP3pp3r

    Programming languages for pussies, use runes

  20. @deadgnom32

    why?

Join the discussion →

Related deep dives