Find longest Palindromic substring technical interview question i got from daily interview pro
A palindrome is a sequence of characters that reads the same backwards and forwards. Given a string, s, find the longest palindromic substring in s.
Example:

Input: &quot;banana&quot;
Output: &quot;anana&quot;

Input: &quot;million&quot;
Output: &quot;illi&quot;

i was wondering if there is any way to optimize the code more
Also is using built in functions such as join(),index() and so on not preferred for a coding interview ?Because by using it it makes it so much easier in python compared to languages like java
class Solution: 

    
    def checkOddChars(self,lst):
        ls = []
        for i in lst:
            if lst.count(i) == 1:
                ls.append(i)
        return ls  

    def checkPalindrome(self,lst):
        return lst[:] == lst[::-1]



    def longestPalindrome(self, s):
      lst = list(s)
      
      while lst:
        if self.checkPalindrome(lst) :
            return(''.join(lst))

        oddChars = self.checkOddChars(lst)

        if lst[0] in oddChars:
            del lst[0]
        if lst[len(lst)-1] in oddChars:
            del lst[len(lst)-1]

      return('List is empty')

       
# Test program
s = &quot;tracecars&quot;
print(str(Solution().longestPalindrome(s)))
# racecar
```
def checkPalindrome(self,lst):
    return lst[:] == lst[::-1]

This is an extremely expensive implementation. lst[:] is needlessly creating a copy of lst, then lst[::-1] creates a second complete copy of lst (but reversed). There are many ways of approaching this, but I would go for something like this:
def checkPalindrome(self, lst):
    return all(start == end
               for start, end in zip(lst, reversed(lst)))

It zips lst with a reversed list iterator:
&gt;&gt;&gt; lst = &quot;abcd&quot;
&gt;&gt;&gt; list(zip(lst, reversed(lst)))
[('a', 'd'), ('b', 'c'), ('c', 'b'), ('d', 'a')]

Then checks to see if all the pairs are equal. If they are, the list is a palindrome. This method is still inefficient though since only half of each needs to be checked. It can be further improved by introducing itertools.islice to get a &quot;view&quot; of the first half of the zipped lists:
from itertools import islice

def checkPalindrome(self, lst):
    half_pair_view = islice(zip(lst, reversed(lst)), len(lst) // 2 + 1)
    return all(start == end for start, end in half_pair_view)

islice is like normal list slicing except instead of creating a copy, it just allows you to iterate over a limited portion of the original iterable.
This code is more efficient because every function involved here is &quot;lazy&quot;: they do only as much work as they need to. reversed, zip, islice, and the generator expression all return an iterator that can produce elements (but do little work up front). all also exits as soon as it gets a Falsey result, so it's comparable to a for loop that contains a break in some branches. This is key here because we only want to do as much work as is necessary to determine whether not they're palindromes. Making two full copies of the list does a large amount of work; far more than is required to check if the string is a palindrome.

checkOddChars is a textbook use-case for a list comprehension:
def checkOddChars(self, lst):
    return [i for i in lst if lst.count(i) == 1]

If you ever find yourself initializing an empty list, then iterating over another iterable and adding to the list, you likely want a comprehension.
This is quite an expensive function too. count needs to iterate the entire list each time; once for every element in lst. This also double counts any repeated elements. I'm not sure off the top of my head what a better solution is though.

 del lst[0]

This is also quite expensive. Ideally, you shouldn't delete from a list except for at the very end. Lists don't support efficient deletes, and the closer to the beginning of the list you delete from, the worse it is. I'd switch to using a dequeue instead of a list, which avoids the overhead of popping from the beginning.