Python is highly recognized for being a dynamically typed language, which implies that the datatype of a variable is determined at runtime. In other words, as a Python developer, you are not mandated to declare the data type of the value that a variable accepts because Python realizes the data type of this variable based on the current value it holds.
The flexibility of this feature, however, comes with some disadvantages that you typically would not experience when using a statically typed language like Java or C++:
Python 3.5 introduced type hints, which you can add to your code using the type annotations introduced in Python 3.0. With type hints, you can annotate variables and functions with datatypes. Tools like mypy, pyright, pytypes, or pyre perform the functions of static type-checking and provide hints or warnings when these types are used inconsistently.
This tutorial will explore type hints and how you can add them to your Python code. It will focus on the mypy static type-checking tool and its operations in your code. You’ll learn how to annotate variables, functions, lists, dictionaries, and tuples. You’ll also learn how to work with the Protocol
class, function overloading, and annotating constants.
To get the most out of this tutorial, you should have:
We recommend Python ≥3.10, as those versions have new and better type-hinting features. If you’re using Python ≤3.9, Python provides an alternatives type-hint syntax that I’ll demonstrate in the tutorial.
When declaring a variable in statically-typed languages like C and Java, you are mandated to declare the data type of the variable. As a result, you cannot assign a value that does not conform to the data type you specified for the variable. For example, if you declare a variable to be an integer, you can’t assign a string value to it at any point in time.
int x = 4; x = "hello"; // this would trigger a type error
In statically-typed languages, a compiler monitors the code as it is written and strictly ensures that the developer abides by the rules of the language. If no issues are found, the program can be run.
Using static type-checkers has numerous advantages; some of which include:
Static typing in Python is optional and can be introduced gradually (this is known as gradual typing). With gradual typing, you can choose to specify the portion of your code that should be dynamically or statically typed. The static type-checkers will ignore the dynamically-typed portions of your code and will not give out warnings on code that does not have type hints nor prevents inconsistent types from compiling during runtime.
Since Python is by default, a dynamically-typed language, tools like mypy were created to give you the benefits of a statically-typed environment. mypy is a optional static type checker created by Jukka Lehtosalo. It checks for annotated code in Python and emits warnings if annotated types are used inconsistently.
mypy also checks the code syntax and issues syntax errors when it encounters invalid syntax. Additionally, supports gradual typing, allowing you to add type hints in your code slowly at your own pace.
In Python, you can define a variable with a type hint using the following syntax:
variable_name: type = value
Let’s look at the following variable:
name = "rocket”
You assign a string value "rocket"
to the name
variable.
To annotate the variable, you need to append a colon (:
) after the variable name, and declare a type str
:
name: str = "rocket"
In Python, you can read the type hints defined on variables using the __annotations__
dictionary:
>>> name: str = "rocket" >>> __annotations__ {'name': <class 'str'>}
The __annotations__
dictionary will show you the type hints on all global variables.
As mentioned earlier, the Python interpreter does not enforce types, so defining a variable with a wrong type won’t trigger an error:
>>> name: int = "rocket" >>>
On the other hand, a static type checker like mypy will flag this as an error:
error: Incompatible types in assignment (expression has type "str", variable has type "int")
Declaring type hints for other data types follows the same syntax. The following are some of the simple types you can use to annotate variables:
float
: float values, such as 3.10
int
: integers, such as 3
, 7
str
: strings, such as 'hello'
bool
: boolean value, which can be True
or False
bytes
: represents byte values, such as b'hello'
Annotating variables with simple types like int
, or str
may not be necessary because mypy can infer the type. However, when working with complex datatypes like lists, dictionary or tuples, it is important that you declare type hints to the corresponding variables because mypy may struggle to infer types on those variables.
To annotate a function, declare the annotation after each parameter and the return value:
def function_name(param1: param1_type, param2: param2_type) -> return_type:
Let’s annotate the following function that returns a message:
def announcement(language, version): return f"{language} {version} has been released" announcement("Python", 3.10)
The function accepts a string as the first parameter, a float as the second parameter, and returns a string. To annotate the function parameters, we will append a colon(:
) after each parameter and follow it with the parameter type:
language: str
version: float
To annotate return value type, add ->
immediately after closing the parameter parentheses, just before the function definition colon(:
):
def announcement(language: str, version: float) -> str: ...
The function now has type hints showing that it receives str
and float
arguments, and returns str
.
When you invoke the function, the output should be similar to what is obtained as follows:
result = announcement("Python", 4.11) print(result) # Python 4.11 has been released
Although our code has type hints, the Python interpreter won’t provide warnings if you invoke the function with wrong arguments:
result = announcement(True, "Python") print(result) # True Python has been released
The function executes successfully, even when you passed a Boolean True
as the first argument , and a string "Python"
as the second argument. To receive warnings about these mistakes, we need to use a static type-checker like mypy.
We will now begin our tutorial on static type-checking with mypy to get warnings about type errors in our code.
Create a directory called type_hints
and move it into the directory:
mkdir type_hints && cd type_hints
Create and activate the virtual environment:
python3.10 -m venv venv source venv/bin/activate
Install the latest version of mypy with pip
:
pip install mypy
With mypy installed, create a file called announcement.py
and enter the following code:
def announcement(language, version): return f"{language} {version} has been released" announcement("Python", 3.10)
Save the file and exit. We’re going to reuse the same function from the previous section.
Next, run the file with mypy:
mypy announcement.py Success: no issues found in 1 source file
As you can see, mypy does not emit any warnings. Static typing in Python is optional, and with gradual typing, you should not receive any warnings unless you opt in by adding type hints to functions. This allows you to annotate your code slowly.
Let’s now understand why mypy doesn’t show us any warnings.
Any
typeAs we noted, mypy ignores code with no type hints. This is because it assumes the Any
type on code without hints.
The following is how mypy sees the function:
def announcement(language: Any, version: Any) -> Any: return f"{language} {version} has been released" announcement("Python", 3.10)
The Any
type is a dynamic type that’s compatible with, well, any type. So mypy will not complain whether the function argument types are bool
, int
, bytes
, etc.
Now that we know why mypy doesn’t always issue warnings, let’s configure it to do that.
mypy can be configured to suit your workflow and code practices. You can run mypy in strict mode, using the --strict
option to flag any code without type hints:
mypy --strict announcement.py announcement.py:1: error: Function is missing a type annotation announcement.py:4: error: Call to untyped function "print_release" in typed context Found 2 errors in 1 file (checked 1 source file)
The --strict
option is the most restrictive option and doesn’t support gradual typing. Most of the time, you won’t need to be this strict. Instead, adopt gradual typing to add the type hints in phases.
mypy also provides a --disallow-incomplete-defs
option. This option flags functions that don’t have all of their parameters and return values annotated. This option is so handy when you forget to annotate a return value or a newly added parameter, causing mypy to warn you. You can think of this as your compiler that reminds you to abide by the rules of static typing in your code development.
To understand this, add the type hints to the parameters only and omit the return value types (pretending you forgot):
def announcement(language: str, version: float): return f"{language} {version} has been released" announcement("Python", 3.10)
Run the file with mypy without any command-line option:
mypy announcement.py Success: no issues found in 1 source file
As you can see, mypy does not warn us that we forgot to annotate the return type. It assumes the Any
type on the return value. If the function was large, it would be difficult to figure out the type of value it returns. To know the type, we would have to inspect the return value, which is time-consuming.
To protect ourselves from these issues, pass the --disallow-incomplete-defs
option to mypy:
mypy --disallow-incomplete-defs announcement.py announcement.py:1: error: Function is missing a return type annotation Found 1 error in 1 file (checked 1 source file
Now run the file again with the --disallow-incomplete-defs
option enabled:
def announcement(language: str, version: float) -> str: ...
mypy --disallow-incomplete-defs announcement.py Success: no issues found in 1 source file
Not only does the --disallow-incomplete-defs
option warn you about missing type hint, it also flags any datatype-value mismatch. Consider the example below where bool
and str
values are passed as arguments to a function that accepts str
and float
respectively:
def announcement(language: str, version: float) -> str: return f"{language} {version} has been released" announcement(True, "Python") # bad arguments
Let’s see if mypy will warn us about this now:
mypy --disallow-incomplete-defs announcement.py announcement.py:4: error: Argument 1 to "print_release" has incompatible type "bool"; expected "str" announcement.py:4: error: Argument 2 to "print_release" has incompatible type "str"; expected "float" Found 2 errors in 1 file (checked 1 source file)
Great! mypy warns us that we passed the wrong arguments to the function.
Now, let’s eliminate the need to type mypy
with the --disallow-incomplete-defs
option.
mypy allows you save the options in a mypy.ini
file. When running mypy
, it will check the file and run with the options saved in the file.
You don’t necessarily need to add the --disallow-incomplete-defs
option each time you run the file using mypy. Mypy gives you an alternative of adding this configuration in a mypy.ini
file where you can add some mypy configurations.
Create the mypy.ini
file in your project root directory and enter the following code:
[mypy] python_version = 3.10 disallow_incomplete_defs = True
In the mypy.ini
file, we tell mypy that we are using Python 3.10 and that we want to disallow incomplete function definitions.
Save the file in your project, and next time you can run mypy without any command-line options:
mypy announcement.py Success: no issues found in 1 source file
mypy has many options you can add in the mypy
file. I recommend referring to the mypy command line documentation to learn more.
Not all functions have a return statement. When you create a function with no return statement, it still returns a None
value:
def announcement(language: str, version: float): print(f"{language} {version} has been released") result = announcement("Python", 4.11) print(result) # None
The None
value isn’t totally useful as you may not be able to perform an operation with it. It only shows that the function was executed successfully. You can hint that a function has no return type by annotating the return value with None
:
def announcement(language: str, version: float) -> None: ...
When a function accepts a parameter of more than one type, you can use the union character (|
) to separate the types.
For example, the following function accepts a parameter that can be either str
or int
:
def show_type(num): if(isinstance(num, str)): print("You entered a string") elif (isinstance(num, int)): print("You entered an integer") show_type('hello') # You entered a string show_type(3) # You entered an integer
You can invoke the function show_type
with a string or an integer, and the output depends on the data type of the argument it receives.
To annotate the parameter, we will use the union character |
, which was introduced in Python 3.10, to separate the types as follows:
def show_type(num: str | int) -> None: ... show_type('hello') show_type(3)
The union |
now shows that the parameter num
is either str
or int
.
If you’re using Python ≤3.9, you need to import Union
from the typing
module. The parameter can be annotated as follows:
from typing import Union def show_type(num: Union[str, int]) -> None: ...
Not all parameters in a function are required; some are optional. Here’s an example of a function that takes an optional parameter:
def format_name(name: str, title = None) -> str: if title: return f"Name: {title}. {name.title()}" else: return f"Name: {name.title()}" format_name("john doe", "Mr")
The second parameter title
is an optional parameter that has a default value of None
if it receives no argument at the point of invoking the function. The typing
module provides the Optional[<datatype>]
annotation to annotate this optional parameter with a type hint:
parameter_name: Optional[<datatype>] = <default_datatype>
Below is an example of how you can perform this annotation:
from typing import Optional def format_name(name: str, title: Optional[str] = None) -> str: ... format_name("john doe", "Mr")
Python lists are annotated based on the types of the elements they have or expect to have. Starting with Python ≥3.9, to annotate a list, you use the list
type, followed by []
. []
contains the element’s type data type.
For example, a list of strings can be annotated as follows:
names: list[str] = ["john", "stanley", "zoe"]
If you’re using Python ≤3.8, you need to import List
from the typing
module:
from typing import List names: List[str] = ["john", "stanley", "zoe"]
In function definitions, the Python documentation recommends that the list
type should be used to annotate the return types:
def print_names(names: str) -> list[int]: ...
However, for function parameters, the documentation recommends using these abstract collection types:
Iterable
type to annotate function parametersThe Iterable
type should be used when the function takes an iterable and iterates over it.
An iterable is an object that can return one item at a time. Examples range from lists, tuples, and strings to anything that implements the __iter__
method.
You can annotate an Iterable
as follows, in Python ≥3.9:
from collections.abc import Iterable def double_elements(items: Iterable[int]) -> list[int]: return [item * 2 for item in items] print(double_elements([2, 4, 6])) # list print(double_elements((2, 4))) # tuple
In the function, we define the items
parameter and assign it an Iterable[int]
type hint, which specifies that the Iterable
contains int
elements.
The Iterable
type hint accepts anything that has the __iter__
method implemented. Lists and tuples have the method implemented, so you can invoke the double_elements
function with a list or a tuple, and the function will iterate over them.
To use Iterable
in Python ≤3.8, you have to import it from the typing
module:
from typing import Iterable ...
Using Iterable
in parameters is more flexible than if we had a list
type hint or any other objects that implements the __iter__
method. This is because you wouldn’t need to convert a tuple for example, or any other iterable to a list
before passing it into the function.
Sequence
typeA sequence is a collection of elements that allows you to access an item or compute its length.
A Sequence
type hint can accept a list, string, or tuple. This is because they have special methods: __getitem__
and __len__
. When you access an item from a sequence using items[index]
, the __getitem__
method is used. When getting the length of the sequence len(items)
, the __len__
method is used.
In the following example, we use the Sequence[int]
type to accept a sequence that has integer items:
from collections.abc import Sequence def get_last_element(data: Sequence[int]) -> int: return data[-1] first_item = get_last_element((3, 4, 5)) # 5 second_item = get_last_element([3, 8] # 8
This function accepts a sequence and access the last element from it with data[-1]
. This uses the __getitem__
method on the sequence to access the last element.
As you can see, we can call the function with a tuple or list and the function works properly. We don’t have to limit parameters to list
if all the function does is get an item.
For Python ≤3.8, you need to import Sequence
from the typing
module:
from typing import Sequence ...
To add type hints to dictionaries, you use the dict
type followed by [key_type, value_type]
:
For example, the following dictionary has both the key and the value as a string:
person = { "first_name": "John", "last_name": "Doe"}
You can annotate it as follows:
person: dict[str, str] = { "first_name": "John", "last_name": "Doe"}
The dict
type specifies that the person
dictionary keys are of type str
and values are of type str
.
If you’re using Python ≤3.8, you need to import Dict
from the typing
module.
from typing import Dict person: Dict[str, str] = { "first_name": "John", "last_name": "Doe"}
In function definitions, the documentation recommends using dict
as a return type:
def make_student(name: str) -> dict[str, int]: ...
For function parameters, it recommends using these abstract base classes:
Mapping
classIn function parameters, when you use the dict
type hints, you limit the arguments the function can take to only dict
, defaultDict
, or OrderedDict
. But, there are many dictionary subtypes, such as UserDict
and ChainMap
, that can be used similarly.
You can access an element and iterate or compute their length like you can with a dictionary. This is because they implement:
__getitem__
: for accessing an element__iter__
: for iterating__len__
: computing the lengthSo instead of limiting the structures the parameter accepts, you can use a more generic type Mapping
since it accepts:
dict
UserDict
defaultdict
OrderedDict
ChainMap
Another benefit of the Mapping
type is that it specifies that you are only reading the dictionary and not mutating it.
The following example is a function that access items values from a dictionary:
from collections.abc import Mapping def get_full_name(student: Mapping[str, str]) -> str: return f'{student.get("first_name")} {student.get("last_name")}' john = { "first_name": "John", "last_name": "Doe", } get_full_name(john)
The Mapping
type hint in the above function has the [str, str]
depiction that specifies that the student
data structure has keys and values both of type str
.
If you’re using Python ≤3.8, import Mapping
from the typing
module:
from typing import Mapping
MutableMapping
class as a type hintUse MutableMapping
as a type hint in a parameter when the function needs to mutate the dictionary or its subtypes. Examples of mutation are deleting items or changing item values.
The MutableMapping
class accepts any instance that implements the following special methods:
__getitem__
__setitem__
__delitem__
__iter__
__len__
The __delitem__
and __setitem__
methods are used for mutation, and these are methods that separate Mapping
type from the MutableMapping
type.
In the following example, the function accepts a dictionary and mutates it:
from collections.abc import MutableMapping def update_first_name(student: MutableMapping[str, str], first_name: str) -> None: student["first_name"] = first_name john = { "first_name": "John", "last_name": "Doe", } update_first_name(john, "james")
In the function body, the value in the first_name
variable is assigned to the dictionary and replaces the value paired to the first_name
key. Changing a dictionary key value invokes the __setitem__
method.
If you are on Python ≤3.8, import MutableMapping
from the typing
module.
from typing import MutableMapping ...
TypedDict
class as a type hintSo far, we have looked at how to annotate dictionaries with dict
, Mapping
, and MutableMapping
, but most of the dictionaries have only one type: str
. However, dictionaries can contain a combination of other data types.
Here is an example of a dictionary whose keys are of different types:
student = { "first_name": "John", "last_name": "Doe", "age": 18, "hobbies": ["singing", "dancing"], }
The dictionary values range from str
, int
, and list
. To annotate the dictionary, we will use a TypedDict
that was introduced in Python 3.8. It allows us to annotate the value types for each property with a class-like syntax:
from typing import TypedDict class StudentDict(TypedDict): first_name: str last_name: str age: int hobbies: list[str]
We define a class StudentDict
that inherits from TypedDict
. Inside the class, we define each field and its expected type.
With the TypedDict
defined, you can use it to annotate a dictionary variable as follows:
from typing import TypedDict class StudentDict(TypedDict): ... student1: StudentDict = { "first_name": "John", "last_name": "Doe", "age": 18, "hobbies": ["singing", "dancing"], }
You can also use it to annotate a function parameter that expects a dictionary as follows:
def get_full_name(student: StudentDict) -> str: return f'{student.get("first_name")} {student.get("last_name")}'
If the dictionary argument doesn’t match StudentDict
, mypy will show a warning.
A tuple stores a fixed number of elements. To add type hints to it, you use the tuple
type, followed by []
, which takes the types for each elements.
The following is an example of how to annotate a tuple with two elements:
student: tuple[str, int] = ("John Doe", 18)
Regardless of the number of elements the tuple contains, you’re required to declare the type for each one of them.
The tuple
type can be used as a type hint for a parameter or return type value:
def student_info(student: tuple[str, int]) -> None: ...
If your tuple is expected to have an unknown amount of elements of a similar type, you can use tuple[type, ...]
to annotate them:
letters: tuple[str, ...] = ('a', 'h', 'j', 'n', 'm', 'n', 'z')
To annotate a named tuple, you need to define a class that inherits from NamedTuple
. The class fields define the elements and their types:
from typing import NamedTuple class StudentTuple(NamedTuple): name: str age: int john = StudentTuple("John Doe", 33)
If you have a function that takes a named tuple as a parameter, you can annotate the parameter with the named tuple:
def student_info(student: StudentTuple) -> None: name, age = student print(f"Name: {name}\nAge: {age}") student_info(john)
There are times when you don’t care about the argument a function takes. You only care if it has the method you want.
To implement this behavior, you’d use a protocol. A protocol is a class that inherits from the Protocol
class in the typing
module. In the protocol class, you define one or more methods that the static type checker should look for anywhere the protocol type is used.
Any object that implements the methods on the protocol class will be accepted. You can think of a protocol as an interface found in programming languages such as Java, or TypeScript. Python provides predefined protocols, a good example of this is the Sequence
type. It doesn’t matter what kind of object it is, as long as it implements the __getitem__
and __len__
methods, it accepts them.
Let’s consider the following code snippets. Here is an example of a function that calculates age by subtracting the birth year from the current year:
def calc_age(current_year: int, data) -> int: return current_year - data.get_birthyear()
The function takes two parameters: current_year
, an integer, and data
, an object. Within the function body, we find the difference between the current_year
and the value returned from get_birthyear()
method.
Here is an example of a class that implements the get_birthyear
method:
class Person: def __init__(self, name, birthyear): self.name = name self.birthyear = birthyear def get_birthyear(self) -> int: return self.birthyear # create an instance john = Person("john doe", 1996)
This is one example of such a class, but there could be other classes such as Dog
or Cat
that implements the get_birthyear
method. Annotating all the possible types would be cumbersome.
Since we only care about the get_birthyear()
method. To implement this behavior, let’s create our protocol:
from typing import Protocol class HasBirthYear(Protocol): def get_birthyear(self) -> int: ...
The class HasBirthYear
inherits from Protocol
, which is part of the typing
module. To make the Protocol
aware about the get_birthyear
method, we will redefine the method exactly as it is done in the Person
class example we saw earlier. The only exception would be the function body, where we have to replace the body with an ellipsis (...
).
With the Protocol defined, we can use it on the calc_age
function to add a type hint to the data
parameter:
from typing import Protocol class HasBirthYear(Protocol): def get_birthyear(self) -> int: ... def calc_age(current_year: int, data: HasBirthYear) -> int: return current_year - data.get_birthyear()
Now the data
parameter has been annotated with the HasBirthYear
Protocol. The function can now accept any object as long it has the get_birthyear
method.
Here is the full implementation of the code using Protocol
:
from typing import Protocol class HasBirthYear(Protocol): def get_birthyear(self) -> int: ... class Person: def __init__(self, name, birthyear): self.name = name self.birthyear = birthyear def get_birthyear(self) -> int: return self.birthyear def calc_age(current_year: int, data: HasBirthYear) -> int: return current_year - data.get_birthyear() john = Person("john doe", 1996) print(calc_age(2021, john))
Running the code with mypy will give you no issues.
Some functions produce different outputs based on the inputs you give them. For example, let’s look at the following function:
def add_number(value, num): if isinstance(value, int): return value + num elif isinstance(value, list): return [i + num for i in value] print(add_number(3, 4)) # 7 print(add_number([1, 2, 5], 4)) # [5, 6, 9]
When you call the function with an integer as the first argument, it returns an integer. If you invoke the function with a list as the first argument, it returns a list with each element added with the second argument value.
Now, how can we annotate this function? Based on what we know so far, our first instinct would be to use the union syntax:
def add_number(value: int | list, num: int) -> int | list: ...
However, this could be misleading due to its ambiguity. The above code describes a function that accepts an integer as the first argument, and the function returns either a list
or an int
. Similarly, when you pass a list
as the first argument, the function will return either a list
or an int
.
You can implement function overloading to properly annotate this function. With function overloading, you get to define multiple definitions of the same function without the body, add type hints to them, and place them before the main function implementations.
To do this, annotate the function with the overload
decorator from the typing
module. Let’s define two overloads before the add_number
function implementation:
from typing import overload @overload def add_number(value: int, num: int) -> int: ... @overload def add_number(value: list, num: int) -> list: ... def add_number(value, num): if isinstance(value, int): return value + num elif isinstance(value, list): return [i + num for i in value] print(add_number(3, 4)) print(add_number([1, 2, 5], 4)
We define two overloads before the main function add_number
. The overloads parameters are annotated with the appropriate types and their return value types. Their function bodies contains an ellipsis (...
).
The first overload shows that if you pass int
as the first argument, the function will return int
.
@overload def add_number(value: int, num: int) -> int: ...
The second overload shows that if you pass a list
as the first argument, the function will return a list
.
@overload def add_number(value: list, num: int) -> list: ...
Finally, the main add_number
implementation does not have any type hints.
As you can now see, the overloads annotate the function behavior much better than using unions.
At the time of writing, Python does not have an inbuilt way of defining constants. Starting with Python 3.10, you can use the Final
type from the typing
module. This will mean mypy will emit warnings if there are attempts to change the variable value.
from typing import Final MIN: Final = 10 MIN = MIN + 3
Running the code with mypy
with issue a warning:
final.py:5: error: Cannot assign to final name "MIN" Found 1 error in 1 file (checked 1 source file)
This is because we are trying to modify the MIN
variable value to MIN = MIN + 3
.
Note that, without mypy or any static file-checker, Python won’t enforce this and the code will run without any issues:
>>> from typing import Final >>> MIN: Final = 10 >>> MIN = MIN + 3 >>> MIN >>> 13
As you can see, during runtime you can change the variable value MIN
any time. To enforce a constant variable in your codebase, you have to depend on mypy.
While you may be able to add annotations to your code, the third-party modules you use may not have any type hints. As a result, mypy will warn you.
If you receive those warnings, you can use a type comment that will ignore the third-party module code:
import third_party # type ignore
You also have the option of adding type hints with stubs. To learn how to use stubs, see Stub files in the mypy documentation.
This tutorial explored the differences between statically typed and dynamically typed codes. You learned the different approaches you can use to add type hints to your functions and classes. You also learned about static type-checking with mypy and how to add type hints to variables, functions, lists, dictionaries, and tuples as well as working with Protocols, function overloading, and how to annotate constants.
To continue building your knowledge, visit typing — Support for type hints. To learn more about mypy, visit the mypy documentation.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowIt’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.