I’m trying to use parseInt in my JavaScript app to convert user input strings into numbers, but the results are inconsistent and sometimes return NaN or the wrong value. I’m not sure if I’m missing something about how parseInt works with different bases, leading zeros, or non-numeric characters. Can someone explain the correct way to use parseInt and share best practices so I don’t get unexpected results?
parseInt looks simple, but it has a few gotchas that bite people all the time.
Main things to check:
- Missing radix
If you do this:
parseInt(‘08’)
parseInt(‘09’)
In older engines or weird environments, JavaScript treats strings with a leading 0 as octal.
So:
parseInt(‘08’) → 8 in modern browsers, but used to be NaN or 0 in older ones.
To avoid weirdness, always pass the radix:
parseInt(‘08’, 10) // 8
parseInt(‘101’, 2) // 5
If you skip the radix, parseInt tries to guess the base from the string. That is where things get inconsistent.
- Stops at the first invalid character
parseInt reads from the start, stops when it hits something not valid for that base.
parseInt(‘123abc’, 10) // 123
parseInt(‘10px’, 10) // 10
parseInt(‘px10’, 10) // NaN
So if your input has units, commas, spaces in front, or weird characters, you get partial values or NaN.
Use trim if users enter random spaces:
parseInt(userInput.trim(), 10)
- Floats vs ints
parseInt ignores decimals.
parseInt(‘12.9’, 10) // 12
If you want a float, use:
Number(‘12.9’) // 12.9
parseFloat(‘12.9’) // 12.9
If you parse user input that includes decimals and expect the full number, parseInt is the wrong tool.
- Empty strings and weird input
These go to NaN:
parseInt(‘’, 10) // NaN
parseInt(’ ', 10) // NaN
parseInt(‘abc’, 10) // NaN
So always check the result:
const n = parseInt(input, 10)
if (Number.isNaN(n)) {
// handle invalid number
}
- Better approach for decimal user input
If you want to treat almost any numeric looking input as a number, use Number instead.
Number(‘10’) // 10
Number(‘10.5’) // 10.5
Number(‘10px’) // NaN
Number(’ 10.2 ') // 10.2
Typical fix list for your app:
• Always pass a radix: parseInt(str, 10)
• Run .trim() on user input first
• Decide if you want integers or floats, use Number or parseFloat for floats
• Validate with Number.isNaN
• Log the raw input before parsing to see what the user typed
Example:
const raw = inputElement.value
const cleaned = raw.trim()
const value = Number(cleaned) // or parseInt(cleaned, 10)
if (Number.isNaN(value)) {
// show error message
} else {
// safe to use value
}
Once you do these, parseInt stops being “weird” and starts being predictably annoying instead.
parseInt isn’t “weird,” it’s just brutally literal and kinda outdated for typical “user input” use cases.
@ombrasilente already covered the classic stuff (radix, stopping at invalid chars, decimals), so I’ll focus on how it bites in real apps and what to use instead.
1. You probably don’t actually want parseInt at all
For user input, parseInt is usually the wrong tool:
'12.9'→parseIntgives12, silently drops.9'00042'→42, which might be fine, but:' 1,000 '→1because it stops at the comma'10px'→10, which looks valid but is actually half-parsed junk
This is the real trap: you think you got a valid number, but you actually got a partial one. That’s worse than NaN in many cases.
If you care about correctness, Number() is safer:
Number('10') // 10
Number('10.9') // 10.9
Number('10px') // NaN (good: clearly invalid)
Number(' 10.2 ') // 10.2
I actually disagree a bit with leaning too heavily on parseInt + trim. For free-form user input, I’d start with Number() first and only use parseInt where I know I’m dealing with pure integers (like “age”, “count”, etc.).
2. Hidden input issues in real apps
A bunch of “weird” behavior is not actually parseInt, it’s this stuff:
a) Inputs are empty or not what you think
const value = input.value // might be ''
parseInt('', 10) // NaN
So before parsing, log it:
console.log('raw input:', JSON.stringify(input.value));
You’ll often see ' ' or '' when you thought you had '0'.
b) You’re accidentally passing non-strings
parseInt(undefined, 10) // NaN
parseInt(null, 10) // NaN
parseInt(true, 10) // NaN
If input is sometimes undefined, parseInt will just quietly give NaN. Coerce to string explicitly if you must:
parseInt(String(maybeValue), 10)
…but personally, I’d rather fail fast and fix the data flow.
3. Locale & formatting problems
If your users type:
'1,234.56'in US format'1.234,56'in many EU formats
then:
parseInt('1,234.56', 10) // 1
parseInt('1.234,56', 10) // 1
Number('1,234.56') // NaN
Number('1.234,56') // NaN
Both are “wrong” for real money/decimal handling.
If you actually need to support this kind of input, you cannot rely on parseInt or Number directly. You need a cleaning step, e.g.:
function normalizeDecimal(str) {
// very naive US-only example: remove commas
return str.replace(/,/g, ');
}
Number(normalizeDecimal('1,234.56')) // 1234.56
For real-world locales, use proper libraries instead of trying to hand-roll it. Otherwise you’ll end up inventing a very buggy parser at 2am.
4. NaN confusion
Quick check: NaN is toxic. Once it’s in your calculations, everything downstream becomes NaN.
const n = parseInt(userInput, 10);
// later
const total = n * 10; // NaN if n is NaN
You must guard:
const n = Number(userInput.trim());
if (Number.isNaN(n)) {
// handle invalid input: show error, default, etc
} else {
// use n
}
Avoid using isNaN() (without Number.) because it does weird coercions:
isNaN('foo') // true
isNaN('123') // false
Number.isNaN only returns true for actual NaN.
5. Decide your parsing rule on purpose
Instead of “why is parseInt weird,” flip it to:
- Do I want:
- only integers?
- integers and decimals?
- to reject anything with extra junk?
- or to silently accept partial numbers like
'10px'→ 10?
Then:
- Use
Number()when you want “proper” numeric strings only - Use
parseInt(str, 10)when:- you truly want integers
- and you’re ok with it stopping at the first invalid char
- Use a regex or a real parser when the format is structured:
10px,'12kg', etc.
Example stricter approach for an integer field:
const raw = input.value.trim();
// must be a whole number, no decimals, no letters
if (!/^[+-]?\d+$/.test(raw)) {
// show validation error
} else {
const value = Number(raw); // or parseInt(raw, 10)
}
That prevents '10px' or '12.3' from ever getting that far.
So in short, your “weird” behavior is almost certainly:
- implicit base guessing
- partial parsing of dirty strings
- empty / unexpected values
- assuming parseInt is a “general number converter”
Treat parseInt as a very dumb “read an integer prefix from a string” function, and use Number() + validation for most user input. Once you do that, most of the surprises vanish, and the rest are just your own bugs, which is… somehow comforting?
The short version: parseInt is not broken, it is just a low‑level tool that happily accepts messy input and quietly gives you half‑correct numbers. That is the real danger in your app.
Let me add a different angle from what @byteguru and @ombrasilente already covered:
1. Treat parseInt as a tokenizer, not a validator
parseInt behaves like a dumb scanner that says:
“I’ll grab an integer prefix from this string and stop when things get weird.”
So in business logic or form handling, you should never treat its output as “validated” just because it is not NaN.
Example:
const v1 = parseInt('10px', 10); // 10
const v2 = parseInt('10.5', 10); // 10
Both of these are probably invalid for your use case, but they look fine if you just check for NaN. That is more dangerous than a hard failure.
Better pattern:
- Validate the whole string first (regex or stricter checks).
- Then convert with
NumberorparseInt.
const raw = input.value.trim();
// allow optional sign + digits only
if (!/^[+-]?\d+$/.test(raw)) {
// reject: show error
} else {
const n = Number(raw); // or parseInt(raw, 10)
// now n is a genuinely valid integer
}
This avoids the silent partial parsing that both @byteguru and @ombrasilente correctly warned about, but goes one step further: parseInt should not be your first line of defense at all.
2. Decide: strict vs forgiving behavior
A lot of “weird” results come from not deciding what you actually want the parser to do.
Ask yourself:
-
Should
'10px'be:- rejected as invalid, or
- loosely read as
10?
-
Should
'12.9'be:- rejected if you want a count, or
- accepted as a float?
If you want strict behavior (which is usually right for form input):
- For integers: validate with regex, then use
Number(...). - For floats: validate float patterns, then
Number(...)orparseFloat.
If you want forgiving behavior (for quick internal tools):
parseInt(str, 10)is fine when:- You are okay with
123abc→123. - You know your strings will not have tricky localization or units.
- You are okay with
But mixing “strict expectations” with “forgiving parser” is exactly where your current confusion comes from.
3. Stop using parseInt as a general “string to number” helper
Here is where I somewhat disagree with leaning on parseInt at all in modern apps. For user input:
- Use
Number(str)as your default converter. - Only reach for
parseIntwhen the task is literally “get an integer prefix.”
Examples:
Number(' 42 ') // 42
Number('42.7') // 42.7
Number('42px') // NaN ← clearly invalid
parseInt('42px', 10) // 42 ← hides the error
In UI code, a clean NaN is usually easier to reason about than a half‑correct integer quietly sneaking into your calculations.
4. Typical real‑world bug patterns
Beyond what was already mentioned:
a) Chained math with hidden NaN
const tax = parseInt(taxInput.value, 10);
const total = price + tax; // If tax is NaN, total is NaN
This propagates silently. Good pattern:
const taxStr = taxInput.value.trim();
const tax = Number(taxStr);
if (Number.isNaN(tax)) {
// show error and stop
return;
}
const total = price + tax;
b) Wrong assumptions about “default 0”
Sometimes people expect invalid input to “fall back” to 0. parseInt does not do that:
parseInt(', 10) // NaN, not 0
parseInt('abc', 10) // NaN
If you really want “empty means 0,” you must do it explicitly:
const raw = input.value.trim();
const n = raw === ' ? 0 : Number(raw);
5. About the mysterious product title '
You mentioned ' as a product title, which is literally an empty string. In terms of readability and SEO‑friendliness, that is the equivalent of calling your function function() {} with no name and wondering why stack traces are confusing.
If this is meant to be a placeholder for a real product or library name (for example, a number‑parsing helper for your app), here is how to think about it:
Pros of using a dedicated parsing helper instead of raw parseInt:
- Central place for:
- trimming
- radix management
- error reporting / messaging
- Consistent behavior across the entire codebase
- Easier future changes (swap
parseIntforNumber, add locale handling, etc.)
Cons:
- Slight indirection: people need to learn and trust the helper
- If badly designed, it can still hide partial parsing issues
- Unit tests are essential or it just moves the confusion into a different file
So if ' is the intended product name, give it a real, descriptive identifier in code and docs so that your number‑parsing story is explicit and searchable (both for humans and tooling).
6. How this compares to what others said
- @byteguru did a solid job on the classical gotchas: radix, stopping at invalid chars, decimals,
NaN. - @ombrasilente dug more into real‑world pitfalls and suggested preferring
Number()for many cases.
Where I push a bit further is:
- Do not just “fix”
parseIntusage. - Redesign your input pipeline so that:
- You validate strings explicitly.
- You use
Number()orparseFloat()as the main converters. - You only use
parseIntwhere “read an integer prefix” is truly what you want.
Once you treat parseInt as a specialized tool instead of a generic converter, the “weird” behavior pretty much disappears, because you stop putting it in situations where it can silently mislead you.