The pattern here is reversing a number from the right with arithmetic β peeling off one digit at a time with x % 10, shifting it onto a growing reversed value, and shrinking x with x // 10. The trick that makes this efficient is to stop early: you only need to reverse half of x, not all of it, because a palindrome is symmetric. Stop the moment the reversed half catches up to the remaining half β that is the sign you have passed the middle β then compare the two halves. Two arithmetic facts make the whole thing possible: x % 10 always yields the units digit, and every digit you peel restores the number by dividing by ten, so the peeled digits come out in exact reverse order for free.
Brute Force: String Reversal
Time O(n)Space O(n)Turn x into a string, reverse that string, and compare. This is the direct reading of the definition β βdoes x read the same forward and backward?β β and it is correct on every input. The string makes the minus sign a character, so a negative number like -121 becomes "121-" reversed and is naturally not equal; the trailing-zero trap also resolves itself because "10" reversed is "01".
class Solution: def isPalindrome(self, x: int) -> bool: s = str(x) return s == s[::-1]Why it is not ideal: building the string costs O(n) time, where n is the number of digits in x, and the reversed copy costs another O(n) space on top of the original β so the whole input is duplicated in memory. For a 32-bit integer those constants are tiny, but the larger cost is conceptual: the follow-up explicitly asks for a no-string solution, and converting to a string sidesteps the very arithmetic that makes this a βMath & Geometryβ problem. It also cannot extend to very large integers without first paying the full stringification bill for a comparison that only ever needs the outer half.
Reverse Half of the Number
OptimalTime O(log n)Space O(1)Reverse only the second half of x and compare it with the first half. Handle the easy failures up front: a negative number is never a palindrome, and a positive number ending in 0 has a first digit that could never match that trailing 0 (no leading zeros allowed) β so both are rejected immediately. Then peel digits from the right into a running rev and shrink x by dividing by ten, stopping the moment rev reaches x (the middle). Because x is halved each round, the loop runs about as many times as there are half the digits β roughly O(log n) rounds, not O(n). For even-digit palindromes the two halves match exactly; for odd-digit ones the extra middle digit stays in x, so the final check accepts either x == rev or x == rev // 10.
class Solution: def isPalindrome(self, x: int) -> bool: if x < 0 or (x % 10 == 0 and x != 0): return False rev = 0 while rev < x: rev = rev * 10 + x % 10 x //= 10 return x == rev or x == rev // 10Watch the half-reversal on x = 121 β the odd-length case where the middle digit stays behind in x and the halves align only after dropping that digit:
each peel moves one digit from the right of x into the right of rev; the loop stops the moment rev catches up to x, so the halves are compared at the middle β never by scanning the whole number
Reverse the second half of x and compare it with the first half. Two special cases first: a negative number is never a palindrome, and a positive number ending in 0 is never one either. x = 121 is neither, so a palindrome is still possible. Now peel digits off the right end of x and build rev from them, stopping the instant rev catches up to x β that point is the middle where the two halves meet.
Then x = 10 β the trailing-zero edge case caught up front with no peeling at all:
each peel moves one digit from the right of x into the right of rev; the loop stops the moment rev catches up to x, so the halves are compared at the middle β never by scanning the whole number
Now x = 10. Before any peeling, the trailing-zero rule applies: x is positive and its last digit is 0, so x is rejected immediately. The reason: a palindrome whose first digit must mirror that trailing 0 would have to start with 0, and a number never has a leading zero. So the checks fail with no work done.
Why it is O(log n): the loop divides x by ten every iteration and stops once rev catches x, so it peels roughly half of the digits β about log10(x) / 2 rounds, written O(log n) where n is the value of x (each round is constant work). It contrasts sharply with the brute-force string version, which touches and duplicates all n digits. Space is O(1): only the two integer variables x and rev exist, with no array or string. Correctness rests on the two earlier-rejected edge cases being handled before the loop, plus the x == rev // 10 branch that reconciles the odd-length palindrome where the middle digit never leaves x. The guard also naturally accepts x = 0, since 0 is a palindrome and neither rejection condition applies.