Validate IP Address: Parsing IPv4 and IPv6
Given a string queryIP, return "IPv4" if it is a valid IPv4 address, "IPv6" if it is a valid IPv6 address, or "Neither" if it is neither. This is LeetCode 468: Validate IP Address, and it tests your ability to write clean, rule-based string parsing without letting edge cases slip through.
The problem
A valid IPv4 address has the form "x1.x2.x3.x4" where each xi is a decimal number between 0 and 255 (inclusive), with no leading zeros. For example, "172.16.254.1" is valid, but "172.16.254.01" is not because of the leading zero in "01".
A valid IPv6 address has the form "x1:x2:x3:x4:x5:x6:x7:x8" where each xi is a 1-to-4 character hexadecimal string (digits 0-9 and letters a-f or A-F). Leading zeros are allowed in IPv6. For example, "2001:0db8:85a3:0:0:8A2E:0370:7334" is valid.
Why this problem matters
IP address validation is one of those problems that sounds trivial until you start writing the code. The logic itself is simple, but the number of rules you need to enforce is what makes it tricky. You have to handle two completely different formats (IPv4 and IPv6), each with its own set of constraints around delimiters, character sets, segment counts, and numeric ranges.
This kind of rule-based string parsing shows up everywhere in real software: parsing URLs, validating email addresses, checking date formats, and processing configuration files. The skill is not about clever algorithms. It is about writing clean validation functions that handle every case without turning into a tangled mess of nested conditionals.
The problem also tests whether you can keep your code organized. The cleanest approach is to write one function for IPv4 validation and a separate one for IPv6. Trying to handle both formats in a single function leads to confusing, error-prone code.
The key insight
Look at the delimiter first. If the input contains a ".", try to validate it as IPv4. If it contains a ":", try to validate it as IPv6. If it contains neither (or both), return "Neither". This single check splits the problem into two independent sub-problems, each with its own simple validation rules.
For IPv4, split on "." and check:
- Exactly 4 segments.
- Each segment is non-empty, contains only digits, has no leading zeros (unless the segment is just
"0"), and represents a number between 0 and 255.
For IPv6, split on ":" and check:
- Exactly 8 groups.
- Each group is 1 to 4 characters long, and every character is a valid hexadecimal digit.
That is the entire algorithm. No fancy data structures, no recursion, no dynamic programming. Just careful string splitting and validation.
The code
def validIPAddress(queryIP: str) -> str:
def is_ipv4(s: str) -> bool:
parts = s.split(".")
if len(parts) != 4:
return False
for part in parts:
if not part or not part.isdigit():
return False
if len(part) > 1 and part[0] == "0":
return False
if int(part) > 255:
return False
return True
def is_ipv6(s: str) -> bool:
parts = s.split(":")
if len(parts) != 8:
return False
hex_chars = set("0123456789abcdefABCDEF")
for part in parts:
if not part or len(part) > 4:
return False
if not all(c in hex_chars for c in part):
return False
return True
if "." in queryIP:
return "IPv4" if is_ipv4(queryIP) else "Neither"
if ":" in queryIP:
return "IPv6" if is_ipv6(queryIP) else "Neither"
return "Neither"
Why this works
The outer function checks which delimiter is present and routes to the appropriate validator. If the string has a ".", it could only be IPv4. If it has a ":", it could only be IPv6. If it has neither, it cannot be either format.
The is_ipv4 function splits on "." and verifies four things about each segment:
- Non-empty:
split(".")can produce empty strings if the input starts or ends with a dot, or has consecutive dots. An empty segment means the address is invalid. - Digits only:
part.isdigit()rejects segments with letters, spaces, or special characters. It also rejects negative numbers since"-"is not a digit. - No leading zeros: If a segment has more than one character and starts with
"0", it has a leading zero. The only valid segment starting with"0"is the single character"0"itself. - Range check: The integer value must be at most 255. Combined with the digit-only check, this guarantees the segment is between 0 and 255.
The is_ipv6 function splits on ":" and checks two things per group:
- Length: Each group must be 1 to 4 characters. Empty groups and groups longer than 4 characters are invalid.
- Hex characters: Every character must be a valid hexadecimal digit. Note that IPv6 allows both uppercase and lowercase letters.
One subtlety: the isdigit() check in the IPv4 validator rejects strings like "+1" and " 1", which is correct behavior. Some Python developers use int() with a try/except instead, but that would accept strings like "+1" which are not valid IPv4 segments.
Visual walkthrough
Step 1: Detect the delimiter - the input "172.16.254.1" contains ".", so try IPv4 validation.
The string contains a dot, so we split on "." and attempt to validate as IPv4. If it contained ":", we would try IPv6 instead.
Step 2: Split on "." and check the segment count.
Splitting "172.16.254.1" on "." gives exactly 4 segments. IPv4 requires exactly 4. If we got any other count, we would return "Neither" immediately.
Step 3: Validate segment 0 - "172".
"172" has only digits, no leading zero (first digit is "1"), and the numeric value 172 is between 0 and 255. Segment 0 passes.
Step 4: Validate segment 1 - "16".
"16" has only digits, no leading zero, and 16 is between 0 and 255. Segment 1 passes.
Step 5: Validate segment 2 - "254".
"254" has only digits, no leading zero, and 254 is between 0 and 255. Segment 2 passes.
Step 6: Validate segment 3 - "1".
"1" is a single digit, so no leading-zero issue. The value 1 is between 0 and 255. All four segments are valid. Return "IPv4".
Step 7: Now consider an IPv6 input - "2001:0db8:85a3:0:0:8A2E:0370:7334".
This string contains ":" so we split on ":" and attempt IPv6 validation. Splitting gives 8 groups, which is the required count for IPv6.
Step 8: Validate each IPv6 group - 1 to 4 hex characters, no empty groups.
Each group must be 1-4 characters long, and every character must be a hex digit (0-9, a-f, A-F). Leading zeros are allowed in IPv6. "0db8" and "0370" are both valid. "0" alone is valid too. All 8 groups pass. Return "IPv6".
Step 9: A failing example - "256.256.256.256".
Split on "." gives 4 segments, correct count. But "256" exceeds 255. The first invalid segment is enough to return "Neither". No need to check the rest.
Complexity analysis
| Aspect | Time | Space |
|---|---|---|
| Validate | O(n) | O(n) |
Time: You scan the string once to split it, then iterate through each segment to validate. Each character is examined a constant number of times. The total work is proportional to the length of the input string.
Space: The split() call creates a list of segments, and the hex character set is constant-size. The segments together contain all characters of the original string, so the space used is O(n).
Building blocks
Delimiter-based routing
The pattern of checking which delimiter is present and dispatching to a specialized handler appears in many parsing problems. You split the problem at the top level based on a structural property of the input, then handle each case independently. This keeps each handler simple and focused.
Segment validation
The pattern of splitting a string into parts and validating each one independently is reusable across many problems:
parts = s.split(delimiter)
if len(parts) != expected_count:
return False
for part in parts:
if not is_valid_segment(part):
return False
return True
The only thing that changes between problems is the delimiter, the expected count, and the validation rules. In this problem, you use it twice with different parameters for IPv4 and IPv6.
Edge cases
Before submitting, check these:
- Leading zeros in IPv4:
"01.01.01.01"is"Neither". Each segment"01"has a leading zero. - Single zero is valid:
"0.0.0.0"is"IPv4". The segment"0"does not have a leading zero. - Empty segments:
"1..1.1.1"is"Neither". The split produces an empty string between the two consecutive dots. - Trailing delimiter:
"1.1.1.1."is"Neither". The split produces a trailing empty segment. - Values above 255:
"256.0.0.0"is"Neither". The segment"256"exceeds the valid range. - IPv6 with too many groups:
"2001:0db8:85a3:0:0:8A2E:0370:7334:extra"has 9 groups and is"Neither". - IPv6 with empty group:
"2001:0db8:85a3::8A2E:0370:7334"has an empty group from the"::"and is"Neither". (This problem does not support IPv6 shorthand notation.) - Mixed delimiters: A string like
"1.2:3.4"contains both"."and":". The code tries IPv4 first (since"."is checked first), which fails because"2:3"is not a valid number. Result:"Neither". - Hex with invalid characters:
"2001:0db8:85a3:0:0:8G2E:0370:7334"has"G"which is not a hex digit. Returns"Neither".
A common mistake is using int() to validate IPv4 segments instead of isdigit(). The problem is that int("+1") succeeds in Python but "+1" is not a valid IPv4 segment. Stick with isdigit() followed by a range check to avoid accepting inputs that should be rejected.
From understanding to recall
You can see that this problem is really just two independent validators glued together by a delimiter check. The logic is clean and modular. But in an interview, the trap is not the algorithm. It is the edge cases. Leading zeros, empty segments, range boundaries, hex character validation, the difference between IPv4 and IPv6 rules for leading zeros. There are a lot of small details that you need to get right on the first try.
Spaced repetition helps you internalize these details so they come out automatically. After writing the two validators from memory a few times, you stop second-guessing whether "0" is a valid segment or whether IPv6 allows leading zeros. The rules are just there, ready to go.
The segment validation pattern shows up across many string-parsing problems. Once you have it drilled, problems like Restore IP Addresses, Compare Version Numbers, and String to Integer (atoi) become variations on a template you already know.
Related posts
- Restore IP Addresses - Another IP string parsing problem where you partition a digit string into valid segments
- Decode String - String parsing with nested rules and structured output
- String to Integer (atoi) - Parsing with edge cases around signs, whitespace, and overflow