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
63Comment deleted
JavaScript's loose equality is the reason code reviews exist. It's the only place where `017` being unequal to `'017'` is somehow logical
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
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
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
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
Loose equality is fine - until a single leading zero from a CSV turns billing into base‑8 and on‑call into exponential backoff
JS octals: valid only sans '8', because who needs consistent parsing when you have history to honor?
still better than python Comment deleted
Have you ever seen Python moments like that? Comment deleted
ahahaa so funny, silly python developers😂 Comment deleted
how often you write shitty code like that? Comment deleted
Bruh That's literally gh repo about python moments https://github.com/satwikkansal/wtfpython Comment deleted
Try using both spaces and tabs Comment deleted
That's one way to start a flamewar... Comment deleted
Why first one is true and second one is false? Comment deleted
Probably, because of leading zero second is treated like it is base 8 Comment deleted
017 (oct) = 15 (dec) 018 = 18 Comment deleted
I think the weird part is that "017" is implicitly converted to 17 instead of 15 Comment deleted
ig these conversions aren't considered thoroughly. For binaries and hexadecimals it works well. parseInt however recognizes only hexadecimals Comment deleted
Why would base 8 literal with 8 in it be treated as anything other than error... This language is cancer Comment deleted
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. Comment deleted
in old C it wasn't an error either Comment deleted
Day ruined Comment deleted
Surprised? Comment deleted
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 Comment deleted
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 Comment deleted
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. Comment deleted
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. Comment deleted
018 isn't valid octal tho 😭😭😭 it should reject it with an error Comment deleted
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 Comment deleted
https://www.youtube.com/watch?v=FhNwLvCYlY4 Comment deleted
octal vs decimal? Comment deleted
455 257 weekly installations <3 Comment deleted
let’s pretend static comparison doesn’t exist Comment deleted
омагааах Comment deleted
Something was changed in the V8 a couple years ago, if I am not mistaken. Where did you execute it? Comment deleted
It's not V8, it's == vs === Comment deleted
Oh, I see Comment deleted
It still baffles me that so many programming languages use line breaks [as end of statement] to tokenize the code. 🤓 Comment deleted
Your cryptic message looks like some shell script bomb. Comment deleted
this is actually intended to be like that, i don't see the problem with this Comment deleted
Programming languages for pussies, use runes Comment deleted
I would like to hear more 👀 Comment deleted
why? Comment deleted
Classic "I have no fucking clue how my programming language works so I'll make fun of it" moment Comment deleted
because of more syntactic noise? nonsense. you just got used to it. let me bring an example Comment deleted
what is simpler to read? Comment deleted
foo x = let s = sin x c = cos x in 2 * s * c Comment deleted
or Comment deleted
foo x = let { s = sin x; c = cos x; } in 2 * s * c Comment deleted
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 Comment deleted
it's about, the thing, that with more experience you need less syntax for short concise solutions Comment deleted
why? Comment deleted
it has more syntax you don't need to understand either way. and formatting is more machine readable rather than human readable. Comment deleted
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. Comment deleted
for composition too Comment deleted
yeah. people just take Java and write 10000 lines of SOLID code, while the same fits in a mere 200 lines of SQL script Comment deleted
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) Comment deleted
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. Comment deleted
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. Comment deleted
still better than excel Comment deleted
Wow, this post definitely caught my eye. Cant wait to see more from you. 😉 Comment deleted
not really js moment. C did this too back in a day. Comment deleted