This isn't strange or surprising. parseInt takes two arguments, the second one is the radix and map will call with three arguments, the value, the index and the whole array. You just have to know this and it might be different in other languages. [ ... ].map(x => ...) is the right way to do this.
- I know all that, have been programming for 15 years, yet I still would have done the mistake.
- Simple unit tests may very well not catch this bug the first time.
- The language design allows your brain to ignore the index parameter because JS accepts superfluous parameters, which is a terrible decision.
- map() is a mapping primitive. It's supposed to adapt a type to another type so that you can pass it to a monoid. JS breaks this convention.
- Even traditionally not functional languages know better than that and separate iteration from indexing. E.G: python map() just maps, and if you want numbering, you use explicitly enumerate(). Ruby has each() and each_with_index(), etc.
Just another of the numerous sucky things in JS. They are not big, but they accumulate very quickly and make it one of the worse language existing. Certainly the worst of all modern stack languages. It's a shame it has a monopoly on the most awesome platform in the world: the web.
In fact, it's such a big problem the most popular JS projects are all things to avoid coding in JS or workaround JS deficiencies: typescript, coffeescript, jsx, babel, webpack, lowdash ...
> I know all that, have been programming for 15 years, yet I still would have done the mistake.
I have not done it with parseInt per se, but I have made this precise mistake at least twice with passing a function `f` with an optional second argument in `some_array.map(f)`, and it took a while to figure out what was happening each time.
Now that Javascript has proper iterables and iterators and generators, I have been enjoying using versions of `map` and `spreadmap` which take in a callback function and iterables and produce an iterator. Then I can explicitly use `enumerate` if I want it. https://observablehq.com/@jrus/itertools#map
As a (mostly) outsider, I’ve never understood why JavaScript is so popular in web dev circles. There are so many awesome compile-to-JS languages these days (ClojureScript, PureScript, Elm, ReasonML, Scala.js, etc). What makes people want to use JavaScript instead?
Any time you use anything non-native, you add layers of indirection and potentially lots of impedance mismatch between languages/runtimes. For instance, I know all of the ones you mentioned would be non-starters at my current job because it means we would have to wrap all of our JS libraries/APIs in the language of choice, which is a non-trivial amount of work. Plus, debugging becomes more challenging as you're basically having to do it at two levels simultaneously.
Even with all of JavaScript's deficiencies, it's still far easier to just deal with them than to switch languages entirely.
It's permissiveness means you can hit the ground running more easily writing shitty but functional code, and web dev has long been the gateway for new entrants to engineering. People start programming, learn shitty habits from a shitty language ,and then the religious affinity for programming languages that many have starts to kick in.
This obviously doesn't explain every Javascript fan, but the tendency is strong enough to shift the dynamics of the community.
I can't say I relate to the religious fervor with which people defend programming languages. C++ is probably my favorite language to work in, but I absolutely understand where the hatred for it comes from. I readily admit that it's grotesque in many ways and my preference is likely for lack of sustained exposure to certain other modern languages.
Probably just because it's there--you can start writing JavaScript without installing anything, same as you used to be able to do with BASIC on 8-bit micros. Then when you consider how much Visual Basic or VBA code there must be in the world and why that is you can start to see how the JavaScript ecosystem is how it is.
Yes, but again that's the consequence of another bad design: not including spread or another metaprogramming system for this. And not having default values either.
Spread has existed in Python forever under the name of "splat operator", default values as well. Same with ruby.
That what I meant when I said "they accumulate". A bad design decision not only affect the user cognitive load and productivity, but it also cascades to the rest of the language and shapes it.
Whenever I learn about another of these Javascript foot-guns, I come away with a greater sense of awe at the relentless good sense that went into the design of Python. Not sure if it's a kind of super-human Dutch ability to foresee ramifications of design decisions, or if it's just that Python developed organically over a period of time with healthy feedback from smart and invested users.
That’s a mailing list email from 2016, and is a joke about bunch of silly non-standard names for punctuation (e.g. “bang”, “wack”, and “twiddle”).
Obviously occasional people are going to pick up terminology from other communities, and the name “splat” has been gaining popularity recently (I had literally never heard that term before a few years ago, and have been writing Python code since 2002). I occasionally hear British expats in the USA calling elevators “lifts” or baby carriages “prams” or lines of people “queues”. Doesn’t mean those have been common American terms.
This was certainly not called the “splat operator” “forever” as claimed in the previous comment.
> I call curly brackets "mustaches" all the time
I have never heard anyone call these “mustaches”. The common terms are “curly brackets” and “braces”. Nobody is going to have any idea what you are talking about.
But more to the point, someone calling their template library “mustache” because a curly brace has a vaguely mustache-like shape doesn’t remotely imply that people regularly call curly braces “mustaches” or would have any idea what “mustache” meant in the context of someone pronouncing their computer code.
The surprising bit comes in with the fact that:
1. parseInt is used with only a single argument in most cases.
2. Only the first argument of map is used in most cases.
Using multiple parameters for those are so uncommon that it's easy not to realise that it's supported.
_If you know all of that_ it may not be surprising to you, but in most cases there is no need for the average developer to know it, making it very surprising when it crops up like this.
> in most cases there is no need for the average developer to know it
This may come off as arrogant but.. I'll proceed nonetheless. There is a need for the average developer to know the interfaces of the functions they're choosing to use. They're well documented, and the documentation is neither hard to find, nor difficult to read.
There may be an argument here for strongly typed languages -vs- weakly typed ones, where often some of the ambiguity around optional function parameters is eased, but most languages have optional function parameters, this is not in any way unique to JS. And parseInt and [].map are very common JS methods: this is not some obscure API not all devs would be unfamiliar with, these are built-ins.
Instead of thinking about code writing, think about code reading. Every organization has people who wrote buggy code, for a million reasons: they were distracted, they were hurried, they simply aren't that good (at either engineering or the language). Sometimes this stuff gets through review too, particularly in situations with deadlines.
But one of the safeguards here is that clean, intuitive code makes it possible for any engineer who's a little more thoughtful to catch little bugs like this. God knows I've done it dozens of times over the course of my career: while reading code for some other purpose, a block catches my eye as having something off about it, and I dig in and find a bug.
The problem with unintuitiveness,especially when it's baked into the language, is that you don't scrutinize every line of code you encounter with your full brain and attention. Without going on the hunt for "map and Javascript in general are horribly designed, parseInt has a rarely-used second param, root out likely bad uses", my brain on another task would skim right over a map-parseInt call like the above. It reads as if it's correct, and in any sane language it would be.
The part that is relatively unique to JS is where extra arguments passed to a function are silently ignored. In most every other language, the function passed to map takes 1 argument, which is also the common case in JS. But JS always passes 3 arguments to that function. I don't think it's optional parameters that people find confusing, it's the interplay between optional parameters and functions whose interface depends on functions they accept as arguments ignoring extra arguments.
I don't understand why this is downvoted to death without any replies. Sounds like a reasonable opinion but I'd also love to hear why people think it's downvote-worthy.
You might be a dev who isn't very familiar with Javascript, but you still need to fix it. Or you might be having an off day. Or you're writing a critical fix under a lot of pressure and simply forget about the issue. And I'll bet if this code was being reviewed, most devs would still overlook the issue.
There are many real world scenarios where it isn't so easy or clear cut. We're all human, and we make mistakes. In other fields, we try and reduce how easy it is to make these mistakes. Some things will always be dangerous like table saws. But a programming language?
Anyway, the "deal with it"/"get good" mentality just seems like a lack of empathy. Not to mention the arrogant in-group thing of "oh, you don't know <wart x> of my favourite but flawed language? you must not be a good dev". And the fact that fixing something like this could cumulatively save a huge amount of brainpower for current and future devs.
The hilarious thing about the get-good arguments is that it's proponents don't realize that they're talking about expending effort _for nothing in return_, especially in cases like the server-side where nobody is forced to use Javascript.
You can say the same thing to Pythonistas who are fussed by memory management, but the tradeoff of that intuitiveness is performance (which is being somewhat obviated by newer languages like Rust, which have their own challenges to learn). The actual task you're trying to do is more complex, so the knowledge required is too.
In Javascript's case, the thing you're becoming an expert in is the path-dependent history of dumb language decisions by people who never should have been let anywhere near a language. It's beyond me that people don't understand why this would bug some, at the very least because it was so easily avoidable.
There's some interesting history for awareness of the first issue: parseInt used to be a notorious source of bugs and so it used to be more common to see it with a second argument on any project which used a linter in that era.
ES5 deprecated that behaviour and it's been widespread enough that code which started in the IE9+ era now looks like the very old buggy code omitting the radix, so anyone getting started relatively recently quite reasonably may never have needed to learn about it:
I didn't know about the second parameter until I saw it in a TypeScript signature a few months ago, which acted as a sort of warning to me to avoid using it incorrectly.
But TypeScript doesn't catch this specific bug because it's technically correct[1] from an API stand-point, as the second parameter to parseInt is a number and the second argument to map's callback is a number.
For as many situations as TypeScript genuinely helps you, there are just as many situations where it gives you false security, like in this case, and I'm starting to consider not using it anymore. [2]
[1] not actually "the best kind of correct" despite popular television quotes
Yeah, the problem this one time isn't typing but the JS developers implementing map wrong. Map should not take two arguments. There should be a different function like enumerate in in python for this.
There's nothing wrong with Array#map passing callback(val, i, array), it's a legitimate feature, and the two other arguments can be omitted in actual callbacks. It just means we need to be aware of how it works, which is basically true about any programming language, any standard library, and basically any kind of tool that any kind of professional uses, whether abstract or literal: know how to use your own tools, and avoid blaming the wrench because it's a poor excuse for a hammer.
> know how to use your own tools, and avoid blaming the wrench because it's a poor excuse for a hammer
This is, of course, why famously machine shops simply put up signs saying “don't touch the whirring metal” rather than using safety guards and similar measures.
I don't disagree that it's useful to have the extra arguments for map but the part which is really deadly is ignoring extra arguments, even if it's obvious why backwards-compatibility made changing it unlikely. I really wish that JavaScript had implemented keyword-arguments when the benefits had become obvious because when features like map were added a decade or two later they could have been defined as passing arguments by keyword and causing an error when given a function which didn't accept those names.
Why would you use parseInt over Math.round if you only expect a single arg?
Seems like you'd only want to use parseInt if you expect to need radix changes at some point, e.g. converting between hex strings, decimal values, and binary strings
Well, the average developer should use a lint tool with Javascript.
A bunch of confusion points in Javascript (including this one) have been well understood for a long time, best practices have been developed to deal with them, and mature tooling exists to easily enforce them.
Put another way: If you are unsure, go download eslint along with the plugins for your environment, and find/create a ruleset that turns on almost everything.
Hidden, silent, unintuitive behavior that you "just have to know" (and remember each time you might read or write the code) is absolutely strange and surprising in an engineering context. Javascript's deadly combination of unnecessary unintuitiveness (map's ludicrous API) and permissibility (ignoring extra arguments) strikes again.
The permissibility of guessing what malformed code means is actually somewhat defensible in Javascript's case, since the incentive ecosystem of the Web is complex and frontend parsing has long had a tradition of best-effort parsing instead of throwing an exception. But the absurd signature of map here is half the problem, and there's really no good explanation for that than yet another instance of "Javascript is a dumpster fire that should only be used when forced to". Similarly "easy-to-use" languages like Python can afford strong typing and rejecting extra args when not specified because they're not bound by the Web's permissibility. But Python also, for all its flaws, has adults making language decisions and does the sane thing with APIs like map without loss of expressiveness. Adding loop metadata like indices requires an explicit call to enumerate(), trading off an iota of verbosity for intuitiveness, which is one of the most important things in writing code that can remain productive and bug-free.
That's apologist talk pure and simple. The language, silently, does something that's almost certainly wrong. The language has enough information to provide you with a helpful warning or error message that you probably don't want to do this, but instead, it violates the principle of least surprise by just doing the wrong thing instead.
The correct error is something along the lines of:
let numbers = input.iter().map(parseInt).collect::<Vec<_>>();
--> src/main.rs:10:32
|
4 | fn parseInt<T>(input: T, radix: u32) -> Option<i32> where T: AsRef<str> {
| ----------------------------------------------------------------------- takes 2 arguments
...
10 | let numbers = input.iter().map(parseInt).collect::<Vec<_>>();
| ^^^ expected function that takes 1 argument
This isn't rocket science, it's basic language design. From time immemorial engineers have been making mistakes as they write software, so compilers evolved not to pretend otherwise and hope for the best, but to help engineers catch them. Except for one.
The map function fully expects a function with two arguments, as per documentation. Why would passing a function that takes two arguments to map be an error?
The map function has a bad API surface that interacts poorly with the way people expect it to work. In a better designed API, if you needed the index of each yielded value other languages have some kind of enumerate method that will allow people opt-in to getting the index. Then if you needed the replicate the presented situation you would have something like
I understand that this pattern of having extra "helper" arguments is pervasive in JS and might seem less exotic to the community, but I feel it is a problematic approach, as it is foot-gun prone.
The example given is taken from Rust. Rust does not allow overloading (even in terms of optional parameters, I believe). Well, Rust allows overloading by implementing different traits but then forces you to disambiguate where necessary.
Nah, there's better solutions; you can create a window/stride method that converts a sequence into a sequence of sequences and then passes that into a map function that takes a single argument.
If Rust had ergonomic support to pass multiple arguments to a functions you would still not be protected from this bug. Even the type system wouldn't help you there because the index would be of type usize and so would be the radix argument in parseInt (although in reality this would probably be an enum or u8). But this is caused by the interaction of two bad APIs. Even if we have the restriction that it confirms to the over two decades old signature, map should only yield each element of the input and yield back the result of the closure. If you need the index you could chain a call to enumerate before the map and get tuples of value and index. This is what you'd have to do in Rust and Python, for example.
I've developed applications in JS, but don't have extensive experience with it, and I couldn't spot the problem. Frankly, I think if `['1', '2'].map(parseInt)` is both valid and does not result in a collection containing the integers 1 and 2 in that order, that indicates a catastrophic failure of design at some level. These things happen, but usually you have to dig down to a less bog standard example to find it. The "you just have to know" argument is often unavoidably valid, but in this case I think it's way off the mark.
The problem is that this article is attributing this problem to JS, whereas the problem is with either computer science or human decimal bias: i.e. the radix is not a concept invented by JS.
If you think it should be ok for all devs to believe that parseInt === parseDecimalInt then maybe all languages should be decimal-only. That isn't the case though.
All languages I know of _default_ to a radix of 10 though, allowing other radixes where a prefix is added like 0b111010, 0xa7b45fd9, etc.
Seems intuitive that if parseInt allows omitting the radix parameter, that it should default to whatever radix a standard integer primitive would default to.
> Seems intuitive that if parseInt allows omitting the radix parameter, that it should default to whatever radix a standard integer primitive would default to.
But in this case, the radix is _not_ omitted. The map function passes the index of the current iteration as the second parameter to the function it is passed. It is kinda like this:
```
['1','7','11'].map((item, index) => parseInt(item, index))
Map ever calling anything with 3 arguments is absolutely strange and surprising and should be borderline offensive to most anyone familiar with the operation from ANY other language.
The article is not clickbait but the takeaway is easily misinterpreted. Instead of "JavaScript is such a weird and confusing language" it should be "a lot of people are using JavaScript without understanding the method signatures of commonly using methods".
a lot of people are using JavaScript without understanding the method signatures of commonly using methods because JavaScript is such a weird and confusing language that has nonstandard method signatures for standard functions.
A lot of people are using JavaScript expecting it to conform to the principle of least surprise. Map applies a function f to each element and returns the result. If you want to supply a second argument, you either have to partially apply the function or manually supply the arguments. JavaScript's map is different from what the rest of the world calls map, and that's the problem.
I would never have guessed that it was passing the index as a second parameter, I would have expected a compiler error.
This has less to do with "parseInt" than it has to do with "map" in JavaScript. It doesn't work exactly like .map in Java, Ruby, Elixir, etc., because ".map" in JS also passes in the index, and the full array.
The web is a fascinating study of advice that changed over time: there’s something like a half-decade window where the two argument form became common, with unsafe usage before and safe usage after.
this is a relic from issues in ie8, and you no longer need to do this if you're not supporting ie8. hell even if you use ie8 in almost 99% of cases you won't need to bother.
Because leading 0 implies octal (Base-8). Interestingly, this is also the standard interpretation of IPv4 addresses in that form, where each delimited number [octet] in an IP address that start with a leading 0 should be read as octal.
(That behavior contrasts interestingly with IPv6 where the numbers [segments] are only supposed to ever be hexadecimal (Base-16) and leading zeroes are to be ignored.)
(I'm trying to say that this behavior was removed in modern browsers. The only time when modern browsers are allowed to parse strings as a base other than base-10 when the second argument to parseInt is 0 or undefined is when the string starts with 0x or 0X, at which point the browser must parse it as base-16.)
I caught that if it wasn't clear; it wasn't a direct reply but a reply to the whole thread. I was just pointing out why that original behavior existed in the first place and how funny it is that that behavior in the given example is actually more technically correct because IPv4 addresses are supposed to do that.
The radix can be either an integer between 2-36 or false|null|omitted. 0 is falsey, so it's cast to false, which is a valid param equating to 'auto'. 1 is an invalid param.
JS map behaves sanely - It adds extra params, but I've had them be useful every now and then, and I've never had them cause a problem.
Arguments behave... Like arguments behave in JS. Arguments have ALWAYS been variadic and accessible through the "arguments" variable within a function. That's not the "wrong" thing, it's just a thing. If you want named arguments - put them up top. Otherwise you'll get an array-like with everything passed. This is entirely consistent with the language.
Finally - These two things conspire together to do what exactly? This isn't a subtle bug where it works correctly 99% of the time and blows in prod late on a friday. This is an obvious error with even dead simple test cases that clearly show the dev has messed up.
Unit test your shit, or hell, just run it once or twice before using it and you're fine.
---
Basically - you should know how your std library works. That includes JS. I think this is pretty trivial. Worse, I actually find this behavior far more intuitive than something like ConfigureAwait(false) in C#, for example.
Stuff like this is what drives me crazy when I work with dynamic languages like JS or PHP. I have seen a lot of code that looked perfectly fine but suffered from unexpected casts or defaults.
I much prefer languages like C#, C++ or TypeScript where the compiler warns me of such problems.
This has nothing to do with casts or defaults. Map passes multiple parameters and parseInt accepts multiple parameters. Being unaware of the functions you're using is the problem, not some sort of weird gotchas of the language. The code in the title makes multiple assumptions, and those assumptions all proved wrong.
I mean, I get that this particular example isn't really all that mysterious, but I think it's fair to say that "all parameters are optional and extra parameters are ignored" is a Javascript language gotcha.
I would argue Javascript silently accepting more arguments than the function signature declares is kinda a gotcha. And that's what leads people to not realise map is passing extra arguments, because `list.map(a => f(a))` works.
Only because map's third provided argument exists. If map only supplied element and index to the callback, this would pass most type checkers I'm familiar with, and would remain just as confusing.
It's both. Silently accepting variadic arguments increases the odds of something like this happening. This case wouldn't have happened without it.
Though as I said in another comment, best-effort parsing was a decision that arose out of the Web ecosystem and its one of the few things about Javascript's language design that can't br blamed on incompetence.
What does this have to do with best-effort parsing?
"Variadic arguments" (really optional arguments with defaults/signature overloading, which is only arguably the same thing) existed long before JavaScript, and, indeed, the web. Optional arguments are also widely considered to be good/useful.
Yes but with mandatory parameters the programmer would be aware parseInt had a radix in the first place if they had used it even once. That's one advantage of mandatory parameters, but there are of course disadvantages.
I'd argue basically everything about mandatory arguments is worse than having edgecases like this.
Honestly, most languages STILL won't catch this, because the typical pattern is to use method overloading to provide multiple signatures if default arguments aren't supported.
> I'd argue basically everything about mandatory arguments is worse than having edgecases like this.
Any sane language having optional arguments is using keys for opt args. That's what common lisp and OCaml do.
In OCaml you would write:
let parse_int ?(radix = `Dec) string =...
val parse_int : ?radix:[`Bin | `Oct | `Dec | `Hex] -> string -> int
and call it like
parse_int "42"
- 42
parse_int ~radix:`Bin "111"
- 7
List.mapi parse_int ["1"]
- Static error: this expression has type `string -> int`
but expected int -> 'a -> 'b
I think the parent is talking about how C for instance does not have optional parameters (for better or worse), if this was the case for JS everyone who has ever used parseInt would be aware it takes a radix, but then again you also wouldn't be able to just plug it into map arbitrarily.
Not saying one is better than the other, optional parameters can make for much less verbose code, I especially like parameter defaults introduced in ES6.
> I much prefer languages like C#, C++ or TypeScript where the compiler warns me of such problems.
parseInt is defined as accepting a string and an optional radix value, which is numeric. map is defined as providing the value and its index, which is also numeric. Would any of C#, C++, or TypeScript catch that without redefining either parseInt or map to require a more specific type, breaking compatibility with many millions of lines of code around the web?
The thing which would actually catch this would be the mismatch in the number of arguments (modulo someone declaring that third argument as optional) or breaking compatibility to change one of them not to be a basic integer.
Once you work with several languages it’s really hard to keep track of all this stuff. I never know what exactly evaluates to true vs false in PHP or JavaScript for example. Add to that sloppy programmers on your team who don't check their assumptions it’s really easy to have a ton of subtle bugs in your code.
> a higher-order function that applies a given function to each element of a functor, e.g. a list, returning a list of results in the same order.
Since that is not what Array.prototype.map does, it is not map, it is some similar thing that is misnamed as map. If I wanted the index and the array as arguments, I would ask for them.
I would certainly consider this a "gotcha" because it's unexpected that map both deviates from the norm and the language makes it so easy to misuse and hide that misuse.
This reminds me of "array set" in Tcl (a language many probably haven't used in a long time -- or ever). Unlike the normal "set" command in Tcl, which overwrites the full value of the variable, "array set" is actually a merge operation -- it merges in new keys and doesn't get rid of anything. I've seen experienced programmers use "array set" in a loop, leading to awful bugs. This too could be dismissed by saying that they "don't even know how array set works," but actually I place the blame on the language for making it so easy to misuse.
It is extremely awesome that JavaScript has conditioned a ton of the world's programmers to think that needing to compose your function with the identity function to achieve the desired result is not surprising or bad.
I will not be convinced that an implementation of map that gives different results when there are identity functions in the middle is doing the right thing.
>> "If the radix provided is falsy, then by default, radix is set to 10."
The official docs for parseInt says this:
>> An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful — this does not default to 10. [1]
I just found it confusing whether the author meant the default value is 10, or if a falsy parameter (not undefined) turns out to be 10.
So looking at the rest of the documentation, the parameter not being set causes the default radix to be 10 unless the number starts with 0 or 0x, in which case they radix is 8 or 16 respectively. Though newer versions of JS no longer support the octal syntax (it likely just caused bugs for people who had initial zeros in their strings sometimes)
Agreed. Number is not only more obvious than parseInt for other coders but also defaults to base 10, whereas parseInt (at least historically) does not (edit: ...if you've got leading zeros, see post below).
> defaults to base 10, whereas parseInt (at least histortically) does not.
To clarify for others (because this probably sounds crazy): The default is implicit like the rest of JavaScript, it will change base depending on presence of the prefixes 0x (16) and 00 (8), it doesn't seem to have included 0b yet. The confusing bit was octal because as you can imagine some sources might have base 10 padded with zeros, ES5 basically removes implicit octals in parseInt to avoid this issue.
Arguably this is more of a problem with the ambiguous octal prefix than the concept of using prefixes to determine base.
parseInt will use the first few characters in the string as a heuristic to determine the radix. It’s not reliable for some types of common decimal strings(padded zeros will cause erroneous parsing)
This article made me curious to why a Base-1 or Unary Numeral base system has not been implemented in parseInt() as noted by the OP, since the second argument radix must be between 2 and 36:
parseInt('1', 1) // same error for '0' or '|'
> NaN
The result is intriguing still as "NaN" seems to indicate a invalid input in the first parameter instead of a invalid second parameter.
I've found that apparently there is no consensus [1] on Base-1 notation or parsing, although my primary intuition is correct [2] in that a parser could be written that would parse a "1" as a 1 base10, "11" as a 2 base10, "111" and so on. The parser would probably look a lot like a simple length() function, but that could vary with certain base-1 encodings like the ones used by the Golomb Rice compression algorithms, which have each string end in "0" (unary coding).
The extra args passed by map coupled with the optional extra args in standard methods is the cause of a lot of confusion, but this feels like a missed opportunity for demonstrating functional programming. In the end author suggests
['1', '7', '11'].map(numStr => parseInt(numStr));
I think you'd learn something much more useful with
let's work to bring back brevity the answer is that map is passing extra args just use an arrow func to pass args to parseInt explicitly there saved you a minute scrolling to the bottom
tl;dr: parseInt takes an optional second argument, the radix. It defaults to 10 (so the number is parsed in base 10). Since map() passes in each item in the array as the first argument, and the index as the second, you're parsing each string with the radix of whatever the index is.
To quote the tl;dr: ['1', '7', '11'].map(parseInt) doesn’t work as intended because map passes three arguments into parseInt() on each iteration. The second argument index is passed into parseInt as a radix parameter. So, each string in the array is parsed using a different radix.
That's hilarious. Everybody loves the syntactic sugar that makes things easy, until the unexpected point where it makes things very hard.
The syntactic sugar here is that map and parseInt can be called without specifying all parameters. (But, since JS has no function overloading, it's the same function you're calling.)
A bit of a BS comment since there are no languages that will prevent you from misunderstanding the values passed into or returned from whatever methods/procedures/jump destinations you're working with.
More generally, general purpose languages don't provide safety guarantees in the problem domain. They can provide certain guarantees in the solution domain, but it's left to the programmer to compose the elements of the language into a correct solution.
(In my experience, by far the biggest barrier to a correcty solution in the problem domain is that no one actually knows what that is, much less has expressed it. Instead, people express certain specific behaviors they think the system should have, from which the programmer needs to extrapolate the actual requirements -- not straight-forward since to a greater or lessor degree the expressions will be vague, self-contradictory, self-defeating, and/or incoherent. Then they need to compose those requirements in the solution space. BTW, the language is just a part of the solution space and "safe" languages are usually only referring to static checks, which the solution space often consists of distributed components which aren't strictly controlled in lock-step by the same static sources, meaning the static guarantees are useful, but in a limited way.)
This is not one of the weird things about Javascript. When you use a function (like map), always check the documentation to know its behaviour and don't assume it's similar to some similarly named function from another language. End of story.